[英]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
每個導航只允許一個過濾器,因此對於需要多次包含相同導航的情況(例如,在同一導航上包含多個 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))
...將導致customers
的Orders
集合包含所有訂單。
如果其他Order
被加載到相同的上下文中,由於關系修復,更多的Order
可能會被添加到customers.Orders
集合中。 這是不可避免的,因為 EF 的變更跟蹤器是如何工作的。
context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
...其次是...
context.Orders.Where(o => o.IsDeleted).Load();
...將再次導致customers
的Orders
集合包含所有訂單。
過濾器表達式應包含可用作集合的獨立謂詞的謂詞。 一個例子將說明這一點。 假設我們想要包含由Customer
的某些屬性過濾的訂單:
context.Customers.Include(c => c.Orders.Where(o => o.Classification == c.Classification))
它編譯,但它會拋出一個非常技術性的運行時異常,基本上告訴o.Classification == c.Classification
無法翻譯,因為c.Classification
。 必須使用從Order
到Customer
的反向引用重寫查詢:
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 版本中更改。
由於Where
是IEnumerable
上的擴展方法,因此很明顯只能過濾集合。 無法過濾參考導航屬性。 如果我們想獲得訂單並且只在客戶處於活動狀態時填充他們的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.