ASP.NET Core 6 MVC:搜索包含一组标签的帖子

huangapple go评论76阅读模式
英文:

ASP.NET Core 6 MVC : searching for posts that contain a group of tags

问题

以下是您提供的内容的翻译:

我有一个用于创建帖子的Web应用程序,用户可以在帖子上添加尽可能多的标签(前提是标签存在于应用程序的数据库中)。现在我正在尝试实现一个搜索功能,该功能将返回包含该组标签的所有帖子(例如,搜索“Test”和“Another_test”将显示至少包含这两个标签的帖子。)

帖子的模型如下(为简洁起见删除了一些字段):

// Media指的是一个“帖子”
public class Media
{
    public Guid Id { get; set; }
    public string? Title { get; set; }
    public string? Description { get; set; }
    public DateTime? UploadDate { get; set; }
    public List<Tag>? Tags { get; set; }
}

public class Tag
{
    public Guid Id { get; set; }
    public string Title { get; set; } = String.Empty;
    public List<Media>? Medias { get; set; }
}

我正在使用MariaDB作为我的数据库。

最初,我在Stack Overflow上查找了这个类似的帖子。我尝试实现gaiazov的答案如下:

var selectedTagIds = context.Tags
                            .Include(t => t.Medias)
                            .Where(tag => Tags.Contains(tag.Title))
                            .Select(x => x.Id);

var tagPosts = context.Medias
                      .Include(m => m.Tags)
                      .Where(post => !(from selectedTag in selectedTagIds
                                       join tag in post.Tags on selectedTag equals tag.Id 
                                       into postTags
                                       from tag in postTags.DefaultIfEmpty()
                                       select 1).Any());

return tagPosts.OrderByDescending(p => p.UploadDate);

然而,任何搜索都只会返回错误:

未知列'm.Id'在'where'子句中

查看LINQ创建的查询显示以下查询:

SELECT 
    m.Id, m.Description, m.Title, m.UploadDate, 
    t2.MediasId, t2.TagsId, t2.Id, t2.Title
FROM 
    Medias AS m
LEFT JOIN 
    (SELECT m1.MediasId, m1.TagsId, t3.Id, t3.Title
     FROM MediaTag AS m1
     INNER JOIN Tags AS t3 ON m1.TagsId = t3.Id) AS t2 ON m.Id = t2.MediasId
WHERE 
    NOT (EXISTS (SELECT 1
                 FROM Tags AS t
                 LEFT JOIN  
                     (SELECT m0.MediasId, m0.TagsId, t1.Id, t1.Title
                      FROM MediaTag AS m0
                      INNER JOIN Tags AS t1 ON m0.TagsId = t1.Id
                      WHERE m.Id = m0.MediasId --此行引发错误
                     ) AS t0 ON t.Id = t0.Id
                 WHERE t.Title IN ('Test', 'Another_test'))))
ORDER BY 
    m.UploadDate DESC, m.Id, t2.MediasId, t2.TagsId

WHERE m.Id = m0.MediasId 行引发错误,因为 m 似乎不在该连接的范围内。我还尝试在没有该行的情况下运行查询,但仍然没有返回包含这两个标签的所有帖子。

英文:

I have a web app to create a post, as well as tag that post with as many tags as a user wants (given the tag exists in the app's database). I'm now trying to implement a search function that will return all posts that contain that group of tags (e.g. searching for "Test" and "Another_test" will show posts that contain at least both of those tags.)

The post's models looks like this (some fields removed for brevity):

// Media refers to a &#39;post&#39;
public class Media
{
    public Guid Id { get; set; }
    public string? Title { get; set; }
    public string? Description { get; set; }
    public DateTime? UploadDate { get; set; }
    public List&lt;Tag&gt;? Tags { get; set; }
}

public class Tag
{
    public Guid Id { get; set; }
    public string Title { get; set; } = String.Empty;
    public List&lt;Media&gt;? Medias { get; set; }
}

I am using MariaDB as my database.

I initially looked around on SO and found this similar post. I tried to implement gaiazov's answer as below:

var selectedTagIds = context.Tags
                            .Include(t =&gt; t.Medias)
                            .Where(tag =&gt; Tags.Contains(tag.Title))
                            .Select(x =&gt; x.Id);

var tagPosts = context.Medias
                      .Include(m =&gt; m.Tags)
                      .Where(post =&gt; !(from selectedTag in selectedTagIds
                                       join tag in post.Tags on selectedTag equals tag.Id 
                                       into postTags
                                       from tag in postTags.DefaultIfEmpty()
                          			   select 1).Any());

return tagPosts.OrderByDescending(p =&gt; p.UploadDate);

However, any and all searches would simply return an error

> Unknown column 'm.Id' in 'where' clause

Looking at the query that LINQ created shows the following query:

SELECT 
    m.Id, m.Description, m.Title, m.UploadDate, 
    t2.MediasId, t2.TagsId, t2.Id, t2.Title
FROM 
    Medias AS m
LEFT JOIN 
    (SELECT m1.MediasId, m1.TagsId, t3.Id, t3.Title
     FROM MediaTag AS m1
     INNER JOIN Tags AS t3 ON m1.TagsId = t3.Id) AS t2 ON m.Id = t2.MediasId
WHERE 
    NOT (EXISTS (SELECT 1
                 FROM Tags AS t
                 LEFT JOIN  
                     (SELECT m0.MediasId, m0.TagsId, t1.Id, t1.Title
                      FROM MediaTag AS m0
                      INNER JOIN Tags AS t1 ON m0.TagsId = t1.Id
                      WHERE m.Id = m0.MediasId --This line throws an error
                     ) AS t0 ON t.Id = t0.Id
                 WHERE t.Title IN (&#39;Test&#39;, &#39;Another_test&#39;) 
                   AND (t0.Id IS NULL)))
ORDER BY 
    m.UploadDate DESC, m.Id, t2.MediasId, t2.TagsId

The WHERE m.Id = m0.MediasId line causes an error since m appears to not be in the scope of that join. I also tried to run the query without that line and it still didn't return all posts containing the two tags.

答案1

得分: 0

以下是翻译好的代码部分:

现在这是一个真正令人眼花缭乱的查询!然而,我认为你想尝试以下代码:

var selectedTagIds = context.Tags
                            .Include(t => t.Medias)
                            .Where(tag => Tags.Contains(tag.Title))
                            .Select(x => x.Id).ToList(); // 这些最好都在其中一个标签中;
var tagPosts = context.Medias
                      .Include(m => m.Tags)
                      .Where(media => 
                         selectedTagIds.All(id => media.Tags.Select(y=>y.id).Contains(id)).ToList();
                     // 每个 id 必须在其中一个标签中;
英文:

Now that is a truly dizzying query! However, I think you want to try the following :

var selectedTagIds = context.Tags
                            .Include(t =&gt; t.Medias)
                            .Where(tag =&gt; Tags.Contains(tag.Title))
                            .Select(x =&gt; x.Id).ToList(); // These better be in all of them;
var tagPosts = context.Medias
                      .Include(m =&gt; m.Tags)
                      .Where(media =&gt; 
                         selectedTagIds.All(id =&gt; media.Tags.Select(y=&gt;y.id).Contains(id)).ToList();
                     // every single id must be in one of them tags;

答案2

得分: 0

根据您提供的解决方案,应该是:

var selectedTagIds = _context.Tag
                            .Include(t => t.Medias)
                            .Where(tag => Tags.Contains(tag.Title))
                            .Select(x => x.Id);
var tagposts = _context.Media
              .Include(m => m.Tags)
              .Where(media => !(
                             from selectedTagId in selectedTagIds
                             join tag in media.Tags on selectedTagId equals tag.Id 
                             into postTags
                             from tag in postTags.DefaultIfEmpty()
                             where tag.Id == null
                             select 1
                             ).Any()).ToList();

根据文档提到的:

在左外连接中,左侧源序列中的所有元素都会返回,即使右侧序列中没有匹配的元素。要在LINQ中执行左外连接,可以使用DefaultIfEmpty方法与组合连接一起使用,以指定在左侧元素没有匹配时要生成的默认右侧元素。您可以使用null作为任何引用类型的默认值,或者可以指定用户定义的默认类型。

这一行 where tag.Id == null 很重要,不应删除。

基于LongChalk的解决方案,请注意如果执行了 ToList(),您的C#代码将被转化为SQL命令并执行。删除 ToList() 后,代码将正常工作。

var selectedTagIds = _context.Tag
                            .Include(t => t.Medias)
                            .Where(tag => Tags.Contains(tag.Title))
                            .Select(x => x.Id);
            
var tagPosts = _context.Media
                      .Include(m => m.Tags)
                      .Where(media =>
                         selectedTagIds.All(id => media.Tags.Select(y => y.Id).Contains(id))).ToList();

文档链接
图1
图2

英文:

based on the solution you provided
it should be:

var selectedTagIds = _context.Tag
                            .Include(t =&gt; t.Medias)
                            .Where(tag =&gt; Tags.Contains(tag.Title))
                            .Select(x =&gt; x.Id);
var tagposts = _context.Media
              .Include(m =&gt; m.Tags)
              .Where(media =&gt; !(
                             from selectedTagId in selectedTagIds
                             join tag in media.Tags on selectedTagId equals tag.Id 
                             into postTags
                             from tag in postTags.DefaultIfEmpty()
                             where tag.Id == null
                             select 1
                             ).Any()).ToList();

As the document mentioned:

> In a left outer join, all the elements in the left source sequence are
> returned, even if no matching elements are in the right sequence. To
> perform a left outer join in LINQ, use the DefaultIfEmpty method in
> combination with a group join to specify a default right-side element
> to produce if a left-side element has no matches. You can use null as
> the default value for any reference type, or you can specify a
> user-defined default type.

this line where tag.Id == null is important and shound't be deleted

based on LongChalk's solution, notice if ToList() was excued ,your C# codes would be translated into Sql commands and excuted.Delete ToList() and the codes would work

ASP.NET Core 6 MVC:搜索包含一组标签的帖子

var selectedTagIds = _context.Tag
                            .Include(t =&gt; t.Medias)
                            .Where(tag =&gt; Tags.Contains(tag.Title))
                            .Select(x =&gt; x.Id);
            
            var tagPosts = _context.Media
                                  .Include(m =&gt; m.Tags)
                                  .Where(media =&gt;
                                     selectedTagIds.All(id =&gt; media.Tags.Select(y =&gt; y.Id).Contains(id))).ToList();

ASP.NET Core 6 MVC:搜索包含一组标签的帖子

huangapple
  • 本文由 发表于 2023年7月6日 11:48:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76625379.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定