过滤包含在 EF Core 中
Posted
技术标签:
【中文标题】过滤包含在 EF Core 中【英文标题】:Filtering on Include in EF Core 【发布时间】:2017-09-22 21:44:57 【问题描述】:我正在尝试过滤初始查询。我已经嵌套了包含模型的叶子。我正在尝试根据其中一个包含的属性进行过滤。例如:
using (var context = new BloggingContext())
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();
我怎么也说.Where(w => w.post.Author == "me")
?
【问题讨论】:
这个问题已经被EF问过很多次了。这是不可能的,EF Core 仍然没有。 我也遇到了同样的问题,EF Core 2.xx 实现了吗? 现在有全局查询过滤器,但这只有在它们在所有查询中都非常标准时才有用。您可以逐个查询禁用它们,以便它可以用于更简单的事情。 docs.microsoft.com/en-us/ef/core/querying/filters 我已经通过 EF 核心 DBSet 使用 Linq to Entities 解决了我的问题 但这仍然会带回整个表,然后在 Web 服务器的内存中对其进行过滤吗? 【参考方案1】:不可行。
关于这个话题的讨论正在进行中: https://github.com/aspnet/EntityFramework/issues/1833
我建议四处寻找那里列出的任何第 3 方库,例如:https://github.com/jbogard/EntityFramework.Filters
【讨论】:
这些不适用于 EF Core。使用 EF6 可以使用 entityframework-plus.net 看起来该 repo 已迁移到 EF 核心,因此在 github.com/aspnet/EntityFrameworkCore/issues/1833 继续辩论 @PeterHurtony,EF Plus 现在支持 EF Core 中的 IncludeFilter 引用 EF Plus 作为解决方案只是强化了答案关于查看第三方库的观点。不过,EF Plus 可能应该附加到答案中,因为它是一个可以解决许多问题的庞大功能库。【参考方案2】:您还可以反向搜索。
var blogs = context.Author
.Include(author => author.posts)
.ThenInclude(posts => posts.blogs)
.Where(author => author == "me")
.Select(author => author.posts.blogs)
.ToList();
【讨论】:
但如果 Author 是 owntype 则没有 context.Author?【参考方案3】:不确定 Include() 和 ThenInclude(),但使用单个 include 很简单:
var filteredArticles =
context.NewsArticles.Include(x => x.NewsArticleRevisions)
.Where(article => article.NewsArticleRevisions
.Any(revision => revision.Title.Contains(filter)));
希望这会有所帮助!
【讨论】:
当其中一个适合过滤器时,是否会包括每个修订?【参考方案4】:尽管(仍在讨论中)EF Core 不可行,但我已经设法通过 EF Core DbSet 使用 Linq to Entities 来做到这一点。在你的情况下,而不是:
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList()
.. 你将拥有:
await (from blog in this.DbContext.Blogs
from bPost in blog.Posts
from bpAuthor in bPost.Author
where bpAuthor = "me"
select blog)
.ToListAsync();
【讨论】:
这是最体面的答案。【参考方案5】:Entity Framework core 5 是 support filtered Include
的第一个 EF 版本。
工作原理
支持的操作:
Where
OrderBy(Descending)/ThenBy(Descending)
Skip
Take
一些使用示例(来自original feature request 和github commmit) :
每个导航只允许一个过滤器,因此对于需要多次包含相同导航的情况(例如,同一导航上的多个 ThenInclude)只应用一次过滤器,或对该导航应用完全相同的过滤器。
context.Customers
.Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.OrderDetails)
.Include(c => c.Orders).ThenInclude(o => o.Customer)
或
context.Customers
.Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.OrderDetails)
.Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.Customer)
另一个重要提示:
使用新过滤器操作包含的集合被视为已加载。
这意味着,如果启用延迟加载,则从上一个示例中寻址一个客户的 Orders
集合不会触发整个 Orders
集合的重新加载。
此外,同一上下文中的两个后续过滤Include
s 将累积结果。比如……
context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
...接着...
context.Customers.Include(c => c.Orders.Where(o => o.IsDeleted))
...将导致 customers
和 Orders
包含所有订单的集合。
过滤的包含和关系修复
如果其他Order
s 被加载到相同的上下文中,由于关系修复,它们中的更多可能会被添加到customers.Orders
集合中。由于 EF 的变更跟踪器的工作方式,这是不可避免的。
context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
...接着...
context.Orders.Where(o => o.IsDeleted).Load();
...将再次生成 customers
和 Orders
包含所有订单的集合。
过滤器表达式
过滤器表达式应包含可用作集合的独立谓词的谓词。一个例子可以说明这一点。假设我们想要包含由Customer
的某些属性过滤的订单:
context.Customers.Include(c => c.Orders.Where(o => o.Classification == c.Classification))
它可以编译,但它会抛出一个非常技术性的运行时异常,基本上是告诉o.Classification == c.Classification
无法翻译,因为c.Classification
找不到。必须使用从Order
到Customer
的反向引用重写查询:
context.Customers.Include(c => c.Orders.Where(o => o.Classification == o.Customer.Classification))
谓词o => o.Classification == o.Customer.Classification)
是“独立”的,因为它可以用于独立过滤Orders
:
context.Orders.Where(o => o.Classification == o.Customer.Classification) // No one would try 'c.Classification' here
此限制可能会在比当前稳定版本(EF 核心 5.0.7)更高的 EF 版本中发生变化。
什么可以(不)被过滤
由于Where
是IEnumerable
的扩展方法,很明显只能过滤集合。无法过滤参考导航属性。如果我们想获得订单并且只在客户处于活动状态时填充他们的Customer
属性,我们就不能使用Include
:
context.Orders.Include(c => c.Customer.Where( ... // obviously doesn't compile
过滤包含与过滤查询
Filtered Include
引起了人们对它如何影响整个查询的过滤的一些混淆。经验法则是:不会。
声明...
context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
...从上下文中返回所有个客户,而不仅仅是那些未删除订单的客户。 Include
中的过滤器不会影响主查询返回的项目数。
另一方面,声明...
context.Customers
.Where(c => c.Orders.Any(o => !o.IsDeleted))
.Include(c => c.Orders)
...仅返回具有至少一个未删除订单,但 所有 订单在 Orders
集合中的客户。主查询上的过滤器不会影响Include
返回的每个客户的订单。
要获得未删除订单的客户并仅加载他们未删除的订单,两个过滤器都是必需的:
context.Customers
.Where(c => c.Orders.Any(o => !o.IsDeleted))
.Include(c => c.Orders.Where(o => !o.IsDeleted))
过滤的包含和预测
另一个令人困惑的领域是过滤 Include
和预测 (select new ...
) 之间的关系。简单的规则是:投影忽略Include
s,过滤与否。类似的查询...
context.Customers
.Include(c => c.Orders)
.Select(c => new c.Name, c.RegistrationDate )
...将生成不连接到Orders
的 SQL。至于EF,就和……一样。
context.Customers
.Select(c => new c.Name, c.RegistrationDate )
Include
被过滤时会变得混乱,但Orders
也用于投影:
context.Customers
.Include(c => c.Orders.Where(o => !o.IsDeleted))
.Select(c => new
c.Name,
c.RegistrationDate,
OrderDates = c.Orders.Select(o => o.DateSent)
)
人们可能认为OrderDates
仅包含未删除订单的日期,但它们包含所有Orders
的日期。同样,投影完全忽略了Include
。投影和Include
是不同的世界。
这个查询有趣地展示了他们对自己生活的严格程度:
context.Customers
.Include(c => c.Orders.Where(o => !o.IsDeleted))
.Select(c => new
Customer = c,
OrderDates = c.Orders.Select(o => o.DateSent)
)
现在暂停片刻并预测结果...
不那么简单的规则是:投影不会总是忽略Include
。当投影中有一个可以应用Include
的实体时,它被应用。这意味着投影中的Customer
包含其未删除的Orders
,而OrderDates
仍包含所有日期。你猜对了吗?
【讨论】:
很好的答案,但最后一部分让我明白了。你说预测忽略包含,但这是在哪里记录的,我怎样才能绕过它。我喜欢包含过滤器,但为了减少生成的 SQL 中的数据量,我使用投影来仅返回我需要的内容。我需要过滤子集合,所以我需要第二次过滤吗? @PeterKerr 在这种情况下,您可以过滤投影,例如new root.Property1, Children = root.ChildCollection.Where(...).Select(c => new c.ChildProperty1, ... )
。在 EF 核心的文档 (https://docs.microsoft.com/en-us/ef/core/querying/related-data/eager
) 中曾经有一段关于忽略包含的内容,但我再也找不到它了。然而,这是一个合乎逻辑的结果:如果投影不包含实体,Include
应该去哪里?【参考方案6】:
这个任务可以通过两个查询来完成。例如:
var query = _context.Employees
.Where(x =>
x.Schedules.All(s =>
s.ScheduleDate.Month != DateTime.UtcNow.AddMonths(1).Month &&
s.ScheduleDate.Year != DateTime.UtcNow.AddMonths(1).Year) ||
(x.Schedules.Any(s =>
s.ScheduleDate.Month == DateTime.UtcNow.AddMonths(1).Month &&
s.ScheduleDate.Year == DateTime.UtcNow.AddMonths(1).Year) &&
x.Schedules.Any(i => !i.ScheduleDates.Any())));
var employees = await query.ToListAsync();
await query.Include(x => x.Schedules)
.ThenInclude(x => x.ScheduleDates)
.SelectMany(x => x.Schedules)
.Where(s => s.ScheduleDate.Month == DateTime.UtcNow.AddMonths(1).Month &&
s.ScheduleDate.Year == DateTime.UtcNow.AddMonths(1).Year).LoadAsync();
【讨论】:
我认为SelectMany
会导致此处忽略Includes
。检查是否真的包含ScheduleDates
。 Schedules
是,因为他们在SelectMany
,而不是因为Include
。
已签出 ScheduleDates。 count = 11。因此,所有内容都包括在内。如果删除 .ThenInclude,则不包含任何内容且计数为 0【参考方案7】:
我使用了下面的包 使用 Z.EntityFramework.Plus
IncludeFilter 和 IncludeFilterByPath 两种方法都可以使用。
var list = context.Blogs.IncludeFilter(x => x.Posts.Where(y => !y.IsSoftDeleted))
.IncludeFilter(x => x.Posts.Where(y => !y.IsSoftDeleted)
.SelectMany(y => y.Comments.Where(z => !z.IsSoftDeleted)))
.ToList();
这里是https://dotnetfiddle.net/SK934m的例子
或者你可以这样做
GetContext(session).entity
.Include(c => c.innerEntity)
.Select(c => new Entity()
Name = c.Name,
Logo = c.Logo,
InnerEntity= c.InnerEntity.Where(s => condition).ToList()
)
【讨论】:
只是好奇,你能指出这个库在过滤Include
时是否有任何附加值?是不是比 EF 过滤的Include
做得更好?
是的,这个库给了我们过滤的嵌套列表而不是整个数据,在这种情况下它很好。
但过滤后的 Include 也是如此。
如果你想获得过滤列表的过滤列表,假设教师学生连接在那里,那么如果你想获得分数在50-60之间的学生,那么上面包含过滤器可以用过。
我恢复到我之前的评论。以上是关于过滤包含在 EF Core 中的主要内容,如果未能解决你的问题,请参考以下文章
在 Tweepy Streaming API 中包含过滤条件