筛选 EF Core 中的“包含”

Filtering on Include in EF Core

提问人:Jason N. Gaylord 提问时间:4/26/2017 最后编辑:Ian KempJason N. Gaylord 更新时间:9/23/2022 访问量:119012

问:

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

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

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

C# 实体框架核心

评论

0赞 Hiep Lam 10/15/2018
我遇到了同样的问题,EF Core 2.xx 是否实现了这一点?
0赞 Simon_Weaver 2/14/2019
现在有全局查询筛选器,但这只有在它们在所有查询中都非常标准时才有用。您可以逐个查询地禁用它们,以便它可以用于更简单的事情。learn.microsoft.com/en-us/ef/core/querying/filters
0赞 Kalin Krastev 2/22/2019
我已经解决了通过 EF 核心 DBSet 使用 Linq to Entities 的问题
4赞 tnk479 8/14/2019
但是,这是否仍然会带回整个表,然后在 Web 服务器的内存中对其进行过滤?

答:

30赞 alessalessio 4/26/2017 #1

不可行。

关于这个话题的讨论正在进行中:https://github.com/aspnet/EntityFramework/issues/1833

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

评论

3赞 Peter Hurtony 11/22/2018
这些不适用于 EF Core。使用 EF6 时,它可以与 entityframework-plus.net
0赞 Elijah Lofgren 2/22/2019
看起来该存储库已迁移到 EF 核心,因此争论仍在继续 github.com/aspnet/EntityFrameworkCore/issues/1833
1赞 Jonathan Magnan 7/19/2019
@PeterHurtony,EF Plus 现在支持 EF Core 中的 IncludeFilter
1赞 Captain Prinny 9/4/2019
引用 EF Plus 作为解决方案只是加强了关于查看第三方库的答案的观点。不过,EF Plus可能应该附加到答案中,因为它是一个如此庞大的功能库,可以解决许多问题。
23赞 Frank Horemans 3/31/2018 #2

您也可以撤消搜索。

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

评论

1赞 phiree 9/13/2021
但是,如果 Author 是 ownedtype,则没有上下文。作者?
7赞 Web Dev 12/20/2018 #3

不确定 Include() 和 ThenInclude(),但使用单个 include 很简单:

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

希望这有帮助!

评论

4赞 liqSTAR 9/22/2020
当其中一个适合过滤器时,这难道不包括每个修订版吗?
6赞 Kalin Krastev 2/22/2019 #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();

评论

0赞 pantonis 2/10/2021
这是最体面的答案。
255赞 Gert Arnold 4/11/2020 #5

Entity Framework core 5 是第一个支持筛选 Include 的 EF 版本。

运作方式

支持的操作:

  • 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)

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)

另一个重要提示:

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

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

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

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

...其次。。。

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

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

筛选的包含和关系修复

如果将其他 s 加载到同一上下文中,则由于关系修复,可能会将更多 s 添加到集合中。这是不可避免的,因为 EF 的更改跟踪器的工作方式。Ordercustomers.Orders

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))

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

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 core 5.0.7) 更高的 EF 版本中可能会更改。

什么可以(不能)过滤

由于是一个扩展方法,很明显只能过滤集合。无法筛选引用导航属性。如果我们想获取订单,并且只在客户处于活动状态时填充他们的属性,我们不能使用:WhereIEnumerableCustomerInclude

context.Orders.Include(o => o.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)

...仅返回至少有一个未删除订单的客户,但其所有订单都在集合中。主查询的筛选器不会影响 返回的每位客户的订单数。OrdersInclude

要获取具有未删除订单且仅加载其未删除订单的客户,这两个过滤器都是必需的:

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

筛选的包含和投影

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

context.Customers
    .Include(c => c.Orders)
    .Select(c => new { c.Name, c.RegistrationDate })

...将生成 SQL 而不加入 。至于EF,它和...Orders

context.Customers
    .Select(c => new { c.Name, c.RegistrationDate })

当 被过滤时,它会变得令人困惑,但在投影中也会使用:IncludeOrders

context.Customers
    .Include(c => c.Orders.Where(o => !o.IsDeleted))
    .Select(c => new 
    { 
        c.Name, 
        c.RegistrationDate,
        OrderDates = c.Orders.Select(o => o.DateSent)
    })

人们可能认为它只包含未删除订单的日期,但它们包含所有 .同样,投影完全忽略了 .投影和是独立的世界。OrderDatesOrdersIncludeInclude

他们过着多么严格的生活,这个问题很有趣地证明了这一点:

context.Customers
    .Include(c => c.Orders.Where(o => !o.IsDeleted))
    .Select(c => new 
    { 
        Customer = c, 
        OrderDates = c.Orders.Select(o => o.DateSent)
    })

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

不那么简单的规则是:预测并不总是忽略。当投影中存在可以应用的图元时,应用该图元。这意味着在投影中包含其未删除的 ,而仍然包含所有日期。你做对了吗?IncludeIncludeCustomerOrdersOrderDates

评论

0赞 Peter Kerr 11/16/2021
很好的答案,但这最后一部分吸引了我。你说投影忽略了 Include,但这记录在哪里,我怎样才能绕过它。我喜欢过滤器 include,但为了减少生成的 SQL 中的数据量,我使用投影来仅返回我需要的内容。我需要筛选子集合,那么是否需要再次筛选?
1赞 Gert Arnold 11/16/2021
@PeterKerr 在这种情况下,您可以在投影中进行过滤,例如 .EF core 的文档 () 中曾经有一段关于忽略的 Includes,但我再也找不到了。然而,这是一个合乎逻辑的结果:如果投影不由实体组成,那么应该去哪里?new { root.Property1, Children = root.ChildCollection.Where(...).Select(c => new { c.ChildProperty1, ... })https://learn.microsoft.com/en-us/ef/core/querying/related-data/eagerInclude
1赞 MDave 2/23/2022
忽略 .Include 将导致像我一样浪费很多时间进行调试。多亏了你的回答,我解决了问题,而不是我做了,然后是.“包含”已应用于订单。.Select(c => c.Orders)....FirstOrDefault()orders = c.Orders
-1赞 Acid_st 10/8/2020 #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();

评论

1赞 Gert Arnold 10/8/2020
我认为这里忽略了原因。检查是否真的包括在内。 是,因为它们在 中,而不是因为 .SelectManyIncludesScheduleDatesSchedulesSelectManyInclude
1赞 Acid_st 10/9/2020
已签出 ScheduleDates。计数 = 11。因此,一切都包括在内。如果删除 .ThenInclude,则不包含任何内容,计数为 0
2赞 CodeByAk 3/26/2021 #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()
                })

评论

0赞 Gert Arnold 3/27/2021
只是好奇,你能指出这个库在过滤方面是否有任何附加值吗?它比 EF 的过滤效果更好吗?IncludeInclude
0赞 CodeByAk 4/7/2021
是的,这个库为我们提供了过滤的嵌套列表而不是整个数据,在这种情况下它很好。
0赞 Gert Arnold 4/7/2021
但这就是过滤的 Include 也这样做的。
0赞 CodeByAk 4/9/2021
如果你想得到过滤列表的过滤列表,假设有师生连接,那么如果你想得到分数在50-60之间的学生,那么上面的包含过滤器可以使用。
1赞 Gert Arnold 4/9/2021
我回到我之前的评论。
0赞 T.S. 7/30/2022 #8

有趣的案例,它奏效了!

如果您有表/模型,其中收集是密码历史记录。可以是多个,也可以是没有。user(int id, int? passwordId, ICollection<PwdHist> passwordHistoryCollection)

和。这通过属性具有准关系。PwdHistory(int id, int UserId, user User)

需要获取,并带有相关的当前密码记录,同时留下历史记录。user


User user = _userTable
    .Include(u => u.Tenant)
    .Include(u => u.PwdHistory.Where(p => p.Id == p.PwdUser.PasswordId)) 
    .Where(u => u.UserName == userName)
    .FirstOrDefault();

最有趣的部分是.Include(u => u.PwdHistory.Where(p => p.Id == p.PwdUser.PasswordId))

  • 使用用户和许多密码
  • 使用用户且无密码
  • 无需用户即可使用
0赞 dcansyn 9/23/2022 #9

我们可以通过扩展使用

public static IQueryable<TEntity> IncludeCondition<TEntity, TProperty>(this IQueryable<TEntity> query, Expression<Func<TEntity, TProperty>> predicate, bool? condition) where TEntity : class where TProperty : class
{
    return condition == true ? query.Include(predicate) : query;
}

用法;

_context.Tables.IncludeCondition(x => x.InnerTable, true)