过滤包含在 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 集合的重新加载。

此外,同一上下文中的两个后续过滤Includes 将累积结果。比如……

context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))

...接着...

context.Customers.Include(c => c.Orders.Where(o => o.IsDeleted))

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

过滤的包含和关系修复

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

context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))

...接着...

context.Orders.Where(o => o.IsDeleted).Load();

...将再次生成 customersOrders 包含所有订单的集合。

过滤器表达式

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

context.Customers.Include(c => c.Orders.Where(o => o.Classification == c.Classification))

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

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 版本中发生变化。

什么可以(不)被过滤

由于WhereIEnumerable 的扩展方法,很明显只能过滤集合。无法过滤参考导航属性。如果我们想获得订单并且只在客户处于活动状态时填充他们的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 ... ) 之间的关系。简单的规则是:投影忽略Includes,过滤与否。类似的查询...

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。检查是否真的包含ScheduleDatesSchedules 是,因为他们在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 中包含过滤条件

LINQ / EF Core 不能在查询中使用 string.Contains

.NET Core 在发布中包含文件夹

如何在第三个查询中包含来自其他两个表的过滤行数?

EF Core - 导致 CS1941 的多个连接条件

在 nuget 包中包含引用的项目 DLL [.Net Core RC3 *.csproj 文件]