过滤包含在EF Core中

Jas*_*ord 29 entity-framework-core

我正在尝试过滤初始查询.我已经嵌套了包含模型的叶子.我正在尝试根据其中一个包含的属性进行过滤.例如:

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .ToList();
}
Run Code Online (Sandbox Code Playgroud)

我怎么能说呢.Where(w => w.post.Author == "me")

Ger*_*old 103

Entity Framework core 5 是第一个支持过滤的IncludeEF 版本。

这个怎么运作

支持的操作:

  • Where
  • OrderBy(Descending)/ThenBy(Descending)
  • Skip
  • Take

一些使用示例(来自原始功能请求github 提交):

每个导航只允许一个过滤器,因此对于需要多次包含相同导航的情况(例如,在同一个导航上包含多个 ThenInclude)仅应用一次过滤器,或为该导航应用完全相同的过滤器。

context.Customers
    .Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.OrderDetails)
    .Include(c => c.Orders).ThenInclude(o => o.Customer)
Run Code Online (Sandbox Code Playgroud)

或者

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)
Run Code Online (Sandbox Code Playgroud)

另一个重要说明:

使用新过滤器操作包含的集合被视为已加载。

这意味着如果启用了延迟加载,Orders则从上一个示例中解决一个客户的集合将不会触发整个Orders集合的重新加载。

此外,Include同一上下文中的两个后续过滤的s 将累积结果。例如...

context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
Run Code Online (Sandbox Code Playgroud)

...其次是...

context.Customers.Include(c => c.Orders.Where(o => o.IsDeleted))
Run Code Online (Sandbox Code Playgroud)

......将导致customersOrders包含所有订单的集合。

过滤的包含和关系修正

如果其他Orders 被加载到相同的上下文中,customers.Orders由于关系 fixup,更多的可能会被添加到集合中。这是不可避免的,因为 EF 的变更跟踪器是如何工作的。

context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
Run Code Online (Sandbox Code Playgroud)

...其次是...

context.Orders.Where(o => o.IsDeleted).Load();
Run Code Online (Sandbox Code Playgroud)

...将再次导致customersOrders包含所有订单的集合。

过滤器表达式

过滤器表达式应包含可用作集合的独立谓词的谓词。一个例子将说明这一点。假设我们想要包含由以下属性过滤的订单Customer

context.Customers.Include(c => c.Orders.Where(o => o.Classification == c.Classification))
Run Code Online (Sandbox Code Playgroud)

它编译,但它会抛出一个非常技术性的运行时异常,基本上是说o.Classification == c.Classification因为c.Classification找不到而无法翻译。必须使用从Orderto的反向引用重写查询Customer

context.Customers.Include(c => c.Orders.Where(o => o.Classification == o.Customer.Classification))
Run Code Online (Sandbox Code Playgroud)

谓词o => o.Classification == o.Customer.Classification)是“独立的”,因为它可以用于Orders独立过滤:

context.Orders.Where(o => o.Classification == o.Customer.Classification) // No one would try 'c.Classification' here
Run Code Online (Sandbox Code Playgroud)

此限制可能会在比当前稳定版本(EF 核心 5.0.7)更高的 EF 版本中更改。

什么可以(不)过滤

由于Where是一个扩展方法,IEnumerable很明显只能过滤集合。无法过滤参考导航属性。如果我们想获得订单并且只Customer在客户活跃时填充他们的属性,我们不能使用Include

context.Orders.Include(c => c.Customer.Where( ... // obviously doesn't compile
Run Code Online (Sandbox Code Playgroud)

过滤包含与过滤查询

FilteredInclude在如何影响整个查询的过滤方面引起了一些混淆。经验法则是:不会。

该声明...

context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
Run Code Online (Sandbox Code Playgroud)

...从上下文中返回所有客户,而不仅仅是订单未删除的客户。中的过滤器Include不会影响主查询返回的项目数。

另一方面,声明...

context.Customers
    .Where(c => c.Orders.Any(o => !o.IsDeleted))
    .Include(c => c.Orders)
Run Code Online (Sandbox Code Playgroud)

...只返回至少有一个未删除的订单但所有订单都在Orders集合中的客户。主查询的过滤器不会影响由 返回的每个客户的订单Include

要获取未删除订单的客户并仅加载他们未删除的订单,需要两个过滤器:

context.Customers
    .Where(c => c.Orders.Any(o => !o.IsDeleted))
    .Include(c => c.Orders.Where(o => !o.IsDeleted))
Run Code Online (Sandbox Code Playgroud)

过滤的包含和投影

另一个令人困惑的领域是过滤Include和投影 ( select new { ... }) 之间的关系。简单的规则是:投影忽略Includes,过滤与否。像这样的查询...

context.Customers
    .Include(c => c.Orders)
    .Select(c => new { c.Name, c.RegistrationDate })
Run Code Online (Sandbox Code Playgroud)

...将在没有连接的情况下生成 SQL Orders。至于EF,它和...

context.Customers
    .Select(c => new { c.Name, c.RegistrationDate })
Run Code Online (Sandbox Code Playgroud)

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)
    })
Run Code Online (Sandbox Code Playgroud)

人们可能期望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)
    })
Run Code Online (Sandbox Code Playgroud)

现在暂停片刻并预测结果......

不那么简单的规则是:预测并不总是忽略Include。当存在于所述突起的实体Include可以被应用,它施加。这意味着Customer在投影中包含其 undeleted Orders,而OrderDates仍包含所有日期。你做对了吗?


ale*_*sio 21

不可行.

有关此主题的讨论正在进行中:https: //github.com/aspnet/EntityFramework/issues/1833

我建议查看那里列出的任何第三方库,例如:https://github.com/jbogard/EntityFramework.Filters

  • 这些不适用于 EF Core。使用 EF6 可以使用 https://entityframework-plus.net/ (3认同)

Fra*_*ans 14

您也可以撤消搜索.

{
    var blogs = context.Author
    .Include(author => author.posts)
        .ThenInclude(posts => posts.blogs)
    .Where(author => author == "me")
    .Select(author => author.posts.blogs)
    .ToList();
}
Run Code Online (Sandbox Code Playgroud)


Ros*_*oss 10

不确定 Include() AND ThenInclude(),但使用单个 include 很容易做到这一点:

var filteredArticles = 
    context.NewsArticles.Include(x => x.NewsArticleRevisions)
    .Where(article => article.NewsArticleRevisions
        .Any(revision => revision.Title.Contains(filter)));
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!

  • 当其中一个版本适合过滤器时,这是否会包括所有修订版本? (4认同)

Kal*_*tev 5

尽管(仍在讨论中)使用 EF Core 不可行,但我已经设法通过 EF Core DbSet 使用 Linq to Entities 来实现。在您的情况下,而不是:

var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .ToList()
Run Code Online (Sandbox Code Playgroud)

.. 你将拥有:

await (from blog in this.DbContext.Blogs
           from bPost in blog.Posts
           from bpAuthor in bPost.Author
           where bpAuthor = "me"
           select blog)
.ToListAsync();
Run Code Online (Sandbox Code Playgroud)