Entity Framework 嵌套集合与另一个集合匹配的核心查询 returns 错误结果
Entity Framework Core query where nested collection matches another collection returns wrong results
我在使用 Entity Framework Core 5.x 时遇到问题,在尝试查找导航 属性 集合包含所有内容的实体集合时,会生成“错误”查询来自另一个列表的元素。
给定以下简化(但标准的多对多)模型
public class Announcement {
public string Id { get; set; }
public string Title { get; set; }
public virtual IEnumerable<AnnouncementTag> Tags { get; set; }
}
public class Tag {
public string Id { get; set; }
public bool IsEnabled { get; set; }
public virtual IEnumerable<AnnouncementTag> Announcements{ get; set; }
}
// Needed since EF Core does not support many-to-many without explicit JOIN class
public class AnnouncementTag {
public string AnnouncementId { get; set; }
public string TagId { get; set; }
public Announcement Announcement { get; set; }
public Tag Tag { get; set; }
}
我想获取 所有标签 与给定列表中的内容匹配的公告列表。
例如用下面的数据
Announcement 1 has Tags A, B
Announcement 2 has Tags B
Announcement 3 has Tags C
查询“带有标签 ["B"] 的所有公告”将 return 公告 2,因为公告 3 没有任何匹配项,而公告 1 仅在一个标签上匹配。
我本以为下面的 LINQ 查询 return 我想要的结果
var applicableTags = new [] { "B" };
var results = db.Announcements.Where(a => a.Tags.All(at => applicableTags.Contains(at.Tag.Id));
但这会导致所有公告返回。只是为了简化查询,我修改为直接use JOIN class (TagId = Tag.Id)
var results = db.Announcements.Where(a => a.Tags.All(at => applicableTags.Contains(at.TagId));
但这会导致所有公告都被 return 编辑。生成的查询看起来很奇怪
SELECT * FROM [Announcements] AS [a]
WHERE NOT EXISTS (
SELECT 1
FROM [AnnouncementTag] AS [a1]
INNER JOIN [Tags] AS [t1] ON [a1].[TagId] = [t1].[Id]
-- Really... <>... opposite of what I would have expected
-- When I query for more than one Tag...say A,B...the <> becomes "NOT IN"
WHERE ([a].[Id] = [a1].[AnnouncementId]) AND ([t1].[Id] <> N'B')
)
我能够用 LINQ-to-Objects 复制这种行为,所以它 而不是 似乎是 Entity Framework 的事情...只是我不明白如何生成查询。
我确实找到了一篇描述以下工作的文章
// Notice how the applicableTags and Tags criteria is flipped from previous query
var results = db.Announcements.Where(a => applicableTags.All(at => a.Tags.Any(t => t.Id == at)));
确实如此……但仅适用于 LINQ-to-Objects。对于 Entity Framework,您会得到以下异常
System.InvalidOperationException: The LINQ expression 'DbSet<Announcement>()
.Where(a => __applicableTags_0
.All(at => DbSet<AnnouncementTag>()
.Where(a0 => EF.Property<string>(a, "Id") != null && object.Equals(
objA: EF.Property<string>(a, "Id"),
objB: EF.Property<string>(a0, "AnnouncementId")))
.Any(a0 => a0.TagId == at)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'
但我不想为了在服务器上进一步过滤而不得不带回 所有 公告。
对我做错了什么有什么建议吗?有人可以解释一下我的原始查询中的缺陷,即 return 出乎意料的结果吗?
假设标签是唯一的,您可以通过以下方式表达您的查询:
var tagCount = applicableTags.Length;
var results = db.Announcements
.Where(a => a.Tags.Where(t => applicableTags.Contains(t.Id)).Count() >= tagCount);
我在使用 Entity Framework Core 5.x 时遇到问题,在尝试查找导航 属性 集合包含所有内容的实体集合时,会生成“错误”查询来自另一个列表的元素。
给定以下简化(但标准的多对多)模型
public class Announcement {
public string Id { get; set; }
public string Title { get; set; }
public virtual IEnumerable<AnnouncementTag> Tags { get; set; }
}
public class Tag {
public string Id { get; set; }
public bool IsEnabled { get; set; }
public virtual IEnumerable<AnnouncementTag> Announcements{ get; set; }
}
// Needed since EF Core does not support many-to-many without explicit JOIN class
public class AnnouncementTag {
public string AnnouncementId { get; set; }
public string TagId { get; set; }
public Announcement Announcement { get; set; }
public Tag Tag { get; set; }
}
我想获取 所有标签 与给定列表中的内容匹配的公告列表。
例如用下面的数据
Announcement 1 has Tags A, B
Announcement 2 has Tags B
Announcement 3 has Tags C
查询“带有标签 ["B"] 的所有公告”将 return 公告 2,因为公告 3 没有任何匹配项,而公告 1 仅在一个标签上匹配。
我本以为下面的 LINQ 查询 return 我想要的结果
var applicableTags = new [] { "B" };
var results = db.Announcements.Where(a => a.Tags.All(at => applicableTags.Contains(at.Tag.Id));
但这会导致所有公告返回。只是为了简化查询,我修改为直接use JOIN class (TagId = Tag.Id)
var results = db.Announcements.Where(a => a.Tags.All(at => applicableTags.Contains(at.TagId));
但这会导致所有公告都被 return 编辑。生成的查询看起来很奇怪
SELECT * FROM [Announcements] AS [a]
WHERE NOT EXISTS (
SELECT 1
FROM [AnnouncementTag] AS [a1]
INNER JOIN [Tags] AS [t1] ON [a1].[TagId] = [t1].[Id]
-- Really... <>... opposite of what I would have expected
-- When I query for more than one Tag...say A,B...the <> becomes "NOT IN"
WHERE ([a].[Id] = [a1].[AnnouncementId]) AND ([t1].[Id] <> N'B')
)
我能够用 LINQ-to-Objects 复制这种行为,所以它 而不是 似乎是 Entity Framework 的事情...只是我不明白如何生成查询。
我确实找到了一篇描述以下工作的文章
// Notice how the applicableTags and Tags criteria is flipped from previous query
var results = db.Announcements.Where(a => applicableTags.All(at => a.Tags.Any(t => t.Id == at)));
确实如此……但仅适用于 LINQ-to-Objects。对于 Entity Framework,您会得到以下异常
System.InvalidOperationException: The LINQ expression 'DbSet<Announcement>()
.Where(a => __applicableTags_0
.All(at => DbSet<AnnouncementTag>()
.Where(a0 => EF.Property<string>(a, "Id") != null && object.Equals(
objA: EF.Property<string>(a, "Id"),
objB: EF.Property<string>(a0, "AnnouncementId")))
.Any(a0 => a0.TagId == at)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'
但我不想为了在服务器上进一步过滤而不得不带回 所有 公告。
对我做错了什么有什么建议吗?有人可以解释一下我的原始查询中的缺陷,即 return 出乎意料的结果吗?
假设标签是唯一的,您可以通过以下方式表达您的查询:
var tagCount = applicableTags.Length;
var results = db.Announcements
.Where(a => a.Tags.Where(t => applicableTags.Contains(t.Id)).Count() >= tagCount);