跟蹤行為控制 Entity Framework Core 是否會在其變更跟蹤器中保存實體實例的相關信息。 如果某個實體被追蹤,則該實體中偵測到的任何變更會在 SaveChanges
過程中保存至資料庫。 EF Core 也會修正追蹤查詢結果中的實體與變更追蹤器中的實體之間的導覽屬性。
備註
無鍵實體類型永遠不會被追蹤。 每當本文提及實體類型時,係指已定義索引鍵的實體類型。
小提示
您可以在 GitHub 上檢視本文 範例。
追蹤查詢
根據預設,傳回實體類型的查詢會追蹤。 追蹤查詢表示實體實例的任何變更都由SaveChanges
保存。 在下列範例中,偵測到部落格評等的變更,並在 SaveChanges
期間保存到資料庫:
var blog = await context.Blogs.SingleOrDefaultAsync(b => b.BlogId == 1);
blog.Rating = 5;
await context.SaveChangesAsync();
當追蹤查詢中傳回結果時,EF Core 會檢查實體是否已經在上下文中。 如果 EF Core 找到現有的實體,則會傳回相同的實例,這可能會使用較少的記憶體,而且比無追蹤查詢更快。 EF Core 不會使用資料庫值覆寫專案中實體屬性的目前和原始值。 如果在內容中找不到實體,EF Core 會建立新的實體實例,並將它附加至內容。 查詢結果不包含任何新增至內容但尚未儲存至資料庫的實體。
無追蹤查詢
如果要在唯讀案例中使用結果,則不追蹤的查詢很實用。 其執行速度通常較快,因為不需要設定變更追蹤資訊。 如果從資料庫擷取的實體不需要更新,則應該使用無追蹤查詢。 個別查詢可以設定為無追蹤。 無追蹤查詢將根據資料庫中的內容提供結果,而不考慮任何本機變更或新增的實體。
var blogs = await context.Blogs
.AsNoTracking()
.ToListAsync();
您可以在內容實例層級變更預設追蹤行為:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var blogs = await context.Blogs.ToListAsync();
下一節說明沒有追蹤查詢的效率可能比追蹤查詢低。
身分識別解析
由於追蹤查詢會使用變更追蹤器,因此EF Core會在追蹤查詢中執行身分識別解析。 當實體具體化時,如果該實體已在變更追蹤器中,EF Core 會從變更追蹤器返回相同的實體實例。 如果結果包含相同的實體多次,則會針對每個出現項目傳回相同的實例。 無追蹤查詢:
- 不要使用變更追蹤器,也不要執行身分識別解析。
- 即使相同的實體在結果中多次出現,也會返回新的實體實例。
您可以在相同的查詢中結合追蹤和無追蹤。 也就是說,您可以進行無追蹤查詢,結果中將包含身分識別解析。 就像可查詢運算子AsNoTracking一樣,我們新增了另一個運算子AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>)。 列舉中 QueryTrackingBehavior 也新增了相關聯的項目。 當使用身分識別解析的查詢設定為沒有追蹤時,在產生查詢結果時,會在背景中使用獨立變更追蹤器,因此每個實例只會具體化一次。 由於此變更追蹤器與上下文中的不同,因此結果不會由上下文追蹤。 完整列舉查詢之後,變更追蹤器會超出範圍,並視需要收集垃圾。
var blogs = await context.Blogs
.AsNoTrackingWithIdentityResolution()
.ToListAsync();
設定預設追蹤行為
如果您發現自己要變更許多查詢的追蹤行為,您可以考慮改變預設值:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying.Tracking;Trusted_Connection=True;ConnectRetryCount=0")
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
這會預設讓所有查詢不進行追蹤。 您仍然可以新增 AsTracking 以進行特定的查詢追蹤。
追蹤和自定義預測
即使查詢的結果類型不是實體類型,EF Core 仍會預設追蹤結果中包含的實體類型。 在下列傳回匿名類型的查詢中,將會追蹤結果集中的Blog
實例。
var blog = context.Blogs
.Select(
b =>
new { Blog = b, PostCount = b.Posts.Count() });
如果結果集包含來自 LINQ 組合的實體類型,EF Core 將會追蹤它們。
var blog = context.Blogs
.Select(
b =>
new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });
如果結果集未包含任何實體類型,則不會進行任何追蹤。 在下列查詢中,我們會傳回匿名類型,其中包含實體的某些值(但沒有實際實體類型的實例)。 查詢中沒有追蹤的實體。
var blog = context.Blogs
.Select(
b =>
new { Id = b.BlogId, b.Url });
EF Core 支援在最上層投影中執行客戶端評估。 如果 EF Core 具體化實體實例以進行客戶端評估,則會加以追蹤。 在這裡,因為我們將實體傳遞 blog
至用戶端方法 StandardizeURL
,因此EF Core也會追蹤部落格實例。
var blogs = await context.Blogs
.OrderByDescending(blog => blog.Rating)
.Select(
blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
.ToListAsync();
public static string StandardizeUrl(Blog blog)
{
var url = blog.Url.ToLower();
if (!url.StartsWith("http://"))
{
url = string.Concat("http://", url);
}
return url;
}
EF Core 不會追蹤結果中包含的無索引鍵實體實例。 但 EF Core 會根據上述規則,追蹤具有索引鍵的實體類型所有其他實例。
舊版
在 3.0 版之前,EF Core 在追蹤的完成方式方面有一些差異。 值得注意的差異如下:
如 客戶端與伺服器評估 頁面所述,EF Core 在 3.0 版之前的任何查詢部分都支援客戶端評估。 用戶端評估導致實體具體化,而實體不屬於結果的一部分。 因此EF Core分析結果以偵測要追蹤的內容。此設計有某些差異,如下所示:
客戶端在投影中進行的評估導致具體化,但未被追蹤的具體化實體實例未返回。 下列範例未追蹤
blog
實體。var blogs = await context.Blogs .OrderByDescending(blog => blog.Rating) .Select( blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) }) .ToListAsync();
在某些情況下,EF Core 不會追蹤來自 LINQ 組合的物件。 下列範例未追蹤
Post
。var blog = context.Blogs .Select( b => new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });
每當查詢結果包含沒有索引鍵的實體類型時,整個查詢都會不進行追蹤。 這意味著,具備索引鍵的實體類型,其結果中的實體也未被追蹤。
EF Core 用來在無追蹤查詢中執行身分識別解析。 它使用弱參考來追蹤已經被傳回的實體。 因此,如果結果集多次包含相同的實體,每次出現時,您將會取得相同的實例。 雖然如果具有相同身分識別的先前結果超出了範圍,並且被垃圾回收,EF Core 會返回新的實例。