简体   繁体   English

过滤包含在 EF Core 中

[英]Filtering on Include in EF Core

I'm trying to filter on the initial query.我正在尝试过滤初始查询。 I have nested include leafs off a model.我已经嵌套了 model 的叶子。 I'm trying to filter based on a property on one of the includes.我正在尝试根据其中一个包含的属性进行过滤。 For example:例如:

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

How can I also say .Where(w => w.post.Author == "me") ?我还能怎么说.Where(w => w.post.Author == "me")

Entity Framework core 5 is the first EF version to support filtered Include . Entity Framework core 5 是第一个支持过滤Include EF 版本。

How it works这个怎么运作

Supported operations:支持的操作:

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

Some usage examples (from the original feature request and the github commmit ) :一些使用示例(来自原始功能请求github 提交):

Only one filter allowed per navigation, so for cases where the same navigation needs to be included multiple times (eg multiple ThenInclude on the same navigation) apply the filter only once, or apply exactly the same filter for that navigation.每个导航只允许一个过滤器,因此对于需要多次包含相同导航的情况(例如,在同一导航上包含多个 ThenInclude)仅应用一次过滤器,或为该导航应用完全相同的过滤器。

context.Customers
    .Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.OrderDetails)
    .Include(c => c.Orders).ThenInclude(o => o.Customer)

or或者

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)

Another important note:另一个重要说明:

Collections included using new filter operations are considered to be loaded.使用新过滤器操作包含的集合被视为已加载。

That means that if lazy loading is enabled, addressing one customer's Orders collection from the last example won't trigger a reload of the entire Orders collection.这意味着如果启用了延迟加载,则从上一个示例中解决一个客户的Orders集合将不会触发整个Orders集合的重新加载。

Also, two subsequent filtered Include s in the same context will accumulate the results.此外,同一上下文中的两个后续过滤的Include将累积结果。 For example...例如...

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

...followed by... ...其次是...

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

...will result in customers with Orders collections containing all orders. ...将导致customersOrders集合包含所有订单。

Filtered Include and relationship fixup过滤的包含和关系修正

If other Order s are loaded into the same context, more of them may get added to a customers.Orders collection because of relationship fixup .如果其他Order被加载到相同的上下文中,由于关系修复,更多的Order可能会被添加到customers.Orders集合中。 This is inevitable because of how EF's change tracker works.这是不可避免的,因为 EF 的变更跟踪器是如何工作的。

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

...followed by... ...其次是...

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

...will again result in customers with Orders collections containing all orders. ...将再次导致customersOrders集合包含所有订单。

The filter expression过滤器表达式

The filter expression should contain predicates that can be used as a stand-alone predicate for the collection.过滤器表达式应包含可用作集合的独立谓词的谓词。 An example will make this clear.一个例子将说明这一点。 Suppose we want to include orders filtered by some property of Customer :假设我们想要包含由Customer的某些属性过滤的订单:

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

It compiles, but it'll throw a very technical runtime exception, basically telling that o.Classification == c.Classification can't be translated because c.Classification can't be found.它编译,但它会抛出一个非常技术性的运行时异常,基本上告诉o.Classification == c.Classification无法翻译,因为c.Classification The query has to be rewritten using a back-reference from Order to Customer :必须使用从OrderCustomer的反向引用重写查询:

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

The predicate o => o.Classification == o.Customer.Classification) is "stand alone" in the sense that it can be used to filter Orders independently:谓词o => o.Classification == o.Customer.Classification)是“独立的”,因为它可以用于独立过滤Orders

context.Orders.Where(o => o.Classification == o.Customer.Classification) // No one would try 'c.Classification' here

This restriction may change in later EF versions than the current stable version (EF core 5.0.7).此限制可能会在比当前稳定版本(EF 核心 5.0.7)更高的 EF 版本中更改。

What can (not) be filtered什么可以(不)过滤

Since Where is an extension method on IEnumerable it's clear that only collections can be filtered.由于WhereIEnumerable上的扩展方法,因此很明显只能过滤集合。 It's not possible to filter reference navigation properties.无法过滤参考导航属性。 If we want to get orders and only populate their Customer property when the customer is active, we can't use Include :如果我们想获得订单并且只在客户处于活动状态时填充他们的Customer属性,我们不能使用Include

context.Orders.Include(c => c.Customer.Where( ... // obviously doesn't compile

Filtered Include vs filtering the query过滤包含与过滤查询

Filtered Include has given rise to some confusion on how it affects filtering a query as a whole. Filtered Include在如何影响整个查询的过滤方面引起了一些混淆。 The rule of the thumb is: it doesn't.经验法则是:不会。

The statement...该声明...

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

...returns all customers from the context, not only the ones with undeleted orders. ...从上下文中返回所有客户,而不仅仅是订单未删除的客户。 The filter in the Include doesn't affect the number of items returned by the main query. Include中的过滤器不会影响主查询返回的项目数。

On the other hand, the statement...另一方面,声明...

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

...only returns customers having at least one undeleted order, but having all of their orders in the Orders collections. ...只返回至少有一个未删除的订单,但所有订单都在Orders集合中的客户。 The filter on the main query doesn't affect the orders per customer returned by Include .主查询的过滤器不会影响Include返回的每个客户的订单。

To get customers with undeleted orders and only loading their undeleted orders, both filters are required:要获取未删除订单的客户并仅加载他们未删除的订单,需要两个过滤器:

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

Filtered Include and projections过滤的包含和投影

Another area of confusion is how filtered Include and projections ( select new { ... } ) are related.另一个令人困惑的领域是过滤后的Include和投影( select new { ... } )是如何相关的。 The simple rule is: projections ignore Include s, filtered or not.简单的规则是:投影忽略Include s,过滤与否。 A query like...像这样的查询...

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

...will generate SQL without a join to Orders . ...将在没有连接到Orders情况下生成 SQL。 As for EF, it's the same as...至于EF,它和...

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

It gets confusing when the Include is filtered, but Orders are also used in the projection: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)
    })

One might expect that OrderDates only contains dates from undeleted orders, but they contain the dates from all Orders .人们可能期望OrderDates只包含来自未删除订单的日期,但它们包含来自所有Orders的日期。 Again, the projection completely ignores the Include .同样,投影完全忽略了Include Projection and Include are separate worlds.投影和Include是不同的世界。

How strictly they lead their own lives is amusingly demonstrated by this query:这个问题有趣地证明了他们过着自己的生活是多么严格:

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

Now pause for a moment and predict the outcome...现在暂停片刻并预测结果......

The not so simple rule is: projections don't always ignore Include .不那么简单的规则是:投影并不总是忽略Include When there is an entity in the projection to which the Include can be applied, it is applied.当投影中有一个实体可以应用Include时,它就会应用。 That means that Customer in the projection contains its undeleted Orders , whereas OrderDates still contains all dates.这意味着投影中的Customer包含其未删除的Orders ,而OrderDates仍包含所有日期。 Did you get it right?你做对了吗?

Not doable.不可行。

There is an on-going discussion about this topic: https://github.com/aspnet/EntityFramework/issues/1833关于这个话题有一个正在进行的讨论: https : //github.com/aspnet/EntityFramework/issues/1833

I'd suggest to look around for any of the 3rd party libraries listed there, ex.: https://github.com/jbogard/EntityFramework.Filters我建议四处看看那里列出的任何 3rd 方库,例如: https : //github.com/jbogard/EntityFramework.Filters

You can also reverse the search.您也可以反向搜索。

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

Not sure about Include() AND ThenInclude(), but it's simple to do that with a single include:不确定 Include() AND ThenInclude(),但使用单个 include 很容易做到这一点:

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

Hope this helps!希望这可以帮助!

Although it's (still in discussion) not doable with EF Core, I've managed to do it using Linq to Entities over EF Core DbSet.尽管(仍在讨论中)使用 EF Core 不可行,但我已经设法通过 EF Core DbSet 使用 Linq to Entities 来实现。 In your case instead of:在您的情况下,而不是:

var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .ToList()

.. you'll have: .. 你将拥有:

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

I used below package Use Z.EntityFramework.Plus我使用了下面的包 Use Z.EntityFramework.Plus

IncludeFilter and IncludeFilterByPath two methods are which you can use.您可以使用 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();

Here is the example https://dotnetfiddle.net/SK934m这是示例https://dotnetfiddle.net/SK934m

Or you can do like this或者你可以这样做

GetContext(session).entity
                .Include(c => c.innerEntity)
                .Select(c => new Entity()
                {
                    Name = c.Name,
                    Logo = c.Logo,
                    InnerEntity= c.InnerEntity.Where(s => condition).ToList()
                })

Interesting case and it worked!!有趣的案例,它奏效了!!

If you have table/model user(int id, int? passwordId, ICollection<PwdHist> passwordHistoryCollection) where collection is history of passwords.如果您有表/模型user(int id, int? passwordId, ICollection<PwdHist> passwordHistoryCollection) ,其中集合是密码历史记录。 Could be many or none.可能很多,也可能没有。

And PwdHistory(int id, int UserId, user User) .PwdHistory(int id, int UserId, user User) This has a quasi relationship via attributes.这通过属性具有准关系。

Needed to get user , with related current password record, while leaving historical records behind.需要获取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();

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

  • works with user and many passwords适用于用户和许多密码
  • works with user and no passwords使用用户并且没有密码
  • works with no user在没有用户的情况下工作

We can use by extension我们可以通过扩展使用

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;
}

Usage;用法;

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

This task can be accomplished with two queries.此任务可以通过两个查询来完成。 For example:例如:

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

I didn't see the simple answer above -- not knocking the other answers.我没有看到上面的简单答案——没有敲其他答案。 The most popular is inclusive for just about any use case.最受欢迎的是几乎适用于任何用例。

C# 9, EF Core 6, was able to do this: C# 9,EF Core 6,能够做到这一点:

                .Include(i => string.Join(", ", i.ApplicablePids.OrderBy(x =>x.CompareTo(i.ApplicablePids))))

I'm new to all this fluent LINQ stuff -- did I miss something here?我对所有这些流利的 LINQ 东西都很陌生——我在这里错过了什么吗?

The easiest inline solution to this I have used would be something like:我使用过的最简单的内联解决方案是这样的:

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .FromSql("Select b.* from Blogs b inner join Posts p on b.BlogId = p.BlogId where p.Author = 'me'")
        .ToList();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM