全域查詢篩選條件
全域查詢篩選準則是元資料模型中套用至實體類型的 LINQ 查詢述詞(通常是 在 OnModelCreating
中)。 查詢述詞是布林運算式,通常傳遞至 LINQ Where
查詢運算子。 EF Core 會自動將這類篩選套用至任何涉及這些實體類型的 LINQ 查詢。 EF Core 也會將它們套用至實體類型,透過使用 Include 或 navigation 屬性間接參考。 此功能的一些常見應用如下:
- 虛刪除 - 實體類型會
IsDeleted
定義屬性。 - 多租使用者 - 實體類型會
TenantId
定義屬性。
範例
下列範例示範如何使用全域查詢篩選,在簡單的部落格模型中實作多租使用者和虛刪除查詢行為。
提示
您可以檢視本文中的 GitHut 範例。
注意
這裡使用多租使用者做為簡單的範例。 另外還有一篇文章,其中包含 EF Core 應用程式中 多租使用者的完整指引 。
首先,定義實體:
public class Blog
{
#pragma warning disable IDE0051, CS0169 // Remove unused private members
private string _tenantId;
#pragma warning restore IDE0051, CS0169 // Remove unused private members
public int BlogId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public bool IsDeleted { get; set; }
public Blog Blog { get; set; }
}
請注意實體上欄位的 _tenantId
Blog
宣告。 此欄位將用來將每個部落格實例與特定租使用者產生關聯。 此外,定義也是 IsDeleted
實體類型上的 Post
屬性。 這個屬性用來追蹤 post 實例是否已「虛刪除」。 亦即,將執行個體標示為已刪除,而不實際移除底層資料。
接下來,使用 HasQueryFilter
API 在 中 OnModelCreating
設定查詢篩選。
modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "_tenantId") == _tenantId);
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
傳遞給呼叫的 HasQueryFilter
述詞運算式現在會自動套用至這些類型的任何 LINQ 查詢。
提示
請注意 DbContext 執行個體層級欄位的使用方式:_tenantId
用來設定目前的租用戶。 模型層級篩選將會使用正確內容執行個體 (亦即,執行查詢的執行個體) 中的值。
注意
目前無法在同一個實體上定義多個查詢篩選準則, 只會套用最後一個查詢篩選準則。 不過,您可以使用邏輯 AND
運算子來定義具有多個條件的單一篩選準則( &&
在 C# 中)。
使用導覽
您也可以在定義全域查詢篩選準則時使用導覽。 在查詢篩選中使用導覽會導致以遞迴方式套用查詢篩選。 當 EF Core 展開查詢篩選中使用的導覽時,它也會套用在參考實體上定義的查詢篩選。
若要以下列方式說明此設定查詢篩選 OnModelCreating
:
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog);
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Posts.Count > 0);
modelBuilder.Entity<Post>().HasQueryFilter(p => p.Title.Contains("fish"));
接下來,查詢所有 Blog
實體:
var filteredBlogs = db.Blogs.ToList();
此查詢會產生下列 SQL,其會套用針對 Blog
和 Post
實體定義的查詢篩選:
SELECT [b].[BlogId], [b].[Name], [b].[Url]
FROM [Blogs] AS [b]
WHERE (
SELECT COUNT(*)
FROM [Posts] AS [p]
WHERE ([p].[Title] LIKE N'%fish%') AND ([b].[BlogId] = [p].[BlogId])) > 0
注意
目前 EF Core 不會偵測全域查詢篩選定義的迴圈,因此在定義它們時應該小心。 如果指定不正確,迴圈可能會導致查詢轉譯期間產生無限迴圈。
使用必要的導覽來存取具有查詢篩選的實體
警告
使用必要的導覽來存取已定義全域查詢篩選的實體,可能會導致非預期的結果。
必要的導覽預期相關實體一律存在。 如果查詢篩選準則篩選出必要的相關實體,父實體也不會產生結果。 因此,您可能會得到比預期少的專案結果。
為了說明問題,我們可以使用 Blog
上述和下列 OnModelCreating
方法指定的 和 Post
實體:
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog).IsRequired();
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Url.Contains("fish"));
此模型可以植入下列資料:
db.Blogs.Add(
new Blog
{
Url = "http://sample.com/blogs/fish",
Posts = new List<Post>
{
new Post { Title = "Fish care 101" },
new Post { Title = "Caring for tropical fish" },
new Post { Title = "Types of ornamental fish" }
}
});
db.Blogs.Add(
new Blog
{
Url = "http://sample.com/blogs/cats",
Posts = new List<Post>
{
new Post { Title = "Cat care 101" },
new Post { Title = "Caring for tropical cats" },
new Post { Title = "Types of ornamental cats" }
}
});
執行兩個查詢時,可以觀察到此問題:
var allPosts = db.Posts.ToList();
var allPostsWithBlogsIncluded = db.Posts.Include(p => p.Blog).ToList();
在上述設定中,第一個查詢會傳回所有 6 Post
秒,但第二個查詢只會傳回 3。 因為第二個查詢中的 方法會載入相關的 Blog
實體,因此會發生這種不相符的情況 Include
。 由於 需要 和 Post
之間的 Blog
流覽,EF Core 會在建構查詢時使用 INNER JOIN
:
SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[IsDeleted], [p].[Title], [t].[BlogId], [t].[Name], [t].[Url]
FROM [Posts] AS [p]
INNER JOIN (
SELECT [b].[BlogId], [b].[Name], [b].[Url]
FROM [Blogs] AS [b]
WHERE [b].[Url] LIKE N'%fish%'
) AS [t] ON [p].[BlogId] = [t].[BlogId]
INNER JOIN
使用篩選會排除全域查詢篩選已移除其相關 Blog
的所有 Post
篩選。
您可以使用選擇性導覽而非必要方式加以定址。
如此一來,第一個查詢會保持不變,不過第二個查詢現在 LEFT JOIN
會產生並傳回 6 個結果。
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog).IsRequired(false);
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Url.Contains("fish"));
替代方法是在 和 Post
實體上 Blog
指定一致的篩選。
如此一來,比對篩選會同時套用至 Blog
和 Post
。 Post
會移除最終處於非預期狀態的 s,且這兩個查詢都會傳回 3 個結果。
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog).IsRequired();
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Url.Contains("fish"));
modelBuilder.Entity<Post>().HasQueryFilter(p => p.Blog.Url.Contains("fish"));
停用篩選條件
可能會使用 IgnoreQueryFilters 運算子來停用個別 LINQ 查詢的篩選條件。
blogs = db.Blogs
.Include(b => b.Posts)
.IgnoreQueryFilters()
.ToList();
限制
全域查詢篩選條件具有下列限制:
- 只能針對繼承階層的根實體類型定義篩選條件。