英文:
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 'post'
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; }
}
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 => 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);
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 ('Test', 'Another_test')
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 => t.Medias)
.Where(tag => Tags.Contains(tag.Title))
.Select(x => x.Id).ToList(); // These better be in all of them;
var tagPosts = context.Medias
.Include(m => m.Tags)
.Where(media =>
selectedTagIds.All(id => media.Tags.Select(y=>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();
英文:
based on the solution you provided
it should be:
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();
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
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();
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论