簡體   English   中英

過濾包含在 EF Core 中

[英]Filtering on Include in EF Core

我正在嘗試過濾初始查詢。 我已經嵌套了 model 的葉子。 我正在嘗試根據其中一個包含的屬性進行過濾。 例如:

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

我還能怎么說.Where(w => w.post.Author == "me")

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)

另一個重要說明:

使用新過濾器操作包含的集合被視為已加載。

這意味着如果啟用了延遲加載,則從上一個示例中解決一個客戶的Orders集合將不會觸發整個Orders集合的重新加載。

此外,同一上下文中的兩個后續過濾的Include將累積結果。 例如...

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

...其次是...

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

...將導致customersOrders集合包含所有訂單。

過濾的包含和關系修正

如果其他Order被加載到相同的上下文中,由於關系修復,更多的Order可能會被添加到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 { ... } )是如何相關的。 簡單的規則是:投影忽略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仍包含所有日期。 你做對了嗎?

不可行。

關於這個話題有一個正在進行的討論: https : //github.com/aspnet/EntityFramework/issues/1833

我建議四處看看那里列出的任何 3rd 方庫,例如: https : //github.com/jbogard/EntityFramework.Filters

您也可以反向搜索。

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

不確定 Include() AND ThenInclude(),但使用單個 include 很容易做到這一點:

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

希望這可以幫助!

盡管(仍在討論中)使用 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();

我使用了下面的包 Use 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()
                })

有趣的案例,它奏效了!!

如果您有表/模型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))

  • 適用於用戶和許多密碼
  • 使用用戶並且沒有密碼
  • 在沒有用戶的情況下工作

我們可以通過擴展使用

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)

此任務可以通過兩個查詢來完成。 例如:

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

我沒有看到上面的簡單答案——沒有敲其他答案。 最受歡迎的是幾乎適用於任何用例。

C# 9,EF Core 6,能夠做到這一點:

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

我對所有這些流利的 LINQ 東西都很陌生——我在這里錯過了什么嗎?

我使用過的最簡單的內聯解決方案是這樣的:

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