追蹤與無追蹤查詢

追蹤行為控制 Entity Framework Core 是否會在其變更追蹤器中保留實體實例的相關資訊。 如果追蹤實體,則實體中偵測到的任何變更會在 期間 SaveChanges 保存至資料庫。 EF Core 也會修正追蹤查詢結果中的實體與變更追蹤器中的實體之間的導覽屬性。

注意

永遠不會追蹤無索引鍵實體類型 。 無論本文提及實體類型,它都會參考已定義索引鍵的實體類型。

提示

您可以檢視本文中的 GitHut 範例

追蹤查詢

預設會追蹤傳回實體類型的查詢。 追蹤查詢表示實體實例的任何變更都由 保存。 SaveChanges 在下列範例中,偵測到部落格評等的變更,並在 期間 SaveChanges 保存到資料庫:

var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();

當追蹤查詢中傳回結果時,EF Core 會檢查實體是否已經在內容中。 如果 EF Core 找到現有的實體,則會傳回相同的實例,這可能會使用較少的記憶體,而且比無追蹤查詢更快。 EF Core 不會使用資料庫值覆寫專案中實體屬性的目前和原始值。 如果在內容中找不到實體,EF Core 會建立新的實體實例,並將它附加至內容。 查詢結果不包含任何新增至內容但尚未儲存至資料庫的實體。

無追蹤查詢

當結果用於唯讀案例時,不追蹤查詢會很有用。 其執行速度通常較快,因為不需要設定變更追蹤資訊。 如果從資料庫擷取的實體不需要更新,則應該使用無追蹤查詢。 個別查詢可以設定為無追蹤。 無追蹤查詢也會根據資料庫中忽略任何本機變更或新增實體的內容來提供結果。

var blogs = context.Blogs
    .AsNoTracking()
    .ToList();

您可以在內容實例層級變更預設追蹤行為:

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

var blogs = context.Blogs.ToList();

下一節說明沒有追蹤查詢的效率可能比追蹤查詢低。

身分識別解析

由於追蹤查詢會使用變更追蹤器,因此 EF Core 會在追蹤查詢中執行身分識別解析。 具體化實體時,如果已追蹤變更追蹤器,EF Core 會從變更追蹤器傳回相同的實體實例。 如果結果包含相同的實體多次,則會針對每個出現專案傳回相同的實例。 無追蹤查詢:

  • 請勿使用變更追蹤器,也不會執行身分識別解析。
  • 即使相同實體包含在結果中多次,也傳回實體的新實例。

您可以在相同的查詢中結合追蹤和無追蹤。 也就是說,您可以有無追蹤查詢,這會在結果中執行身分識別解析。 就像可查詢運算子一樣 AsNoTracking ,我們新增了另一個運算子 AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>) 。 列舉中 QueryTrackingBehavior 也新增了相關聯的專案。 當使用身分識別解析的查詢設定為沒有追蹤時,在產生查詢結果時,會在背景中使用獨立變更追蹤器,因此每個實例只會具體化一次。 由於此變更追蹤器與內容中的追蹤器不同,因此內容不會追蹤結果。 完整列舉查詢之後,變更追蹤器會超出範圍,並視需要收集垃圾。

var blogs = context.Blogs
    .AsNoTrackingWithIdentityResolution()
    .ToList();

設定預設追蹤行為

如果您發現自己要變更許多查詢的追蹤行為,您可以改為變更預設值:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying.Tracking;Trusted_Connection=True")
        .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 = context.Blogs
    .OrderByDescending(blog => blog.Rating)
    .Select(
        blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
    .ToList();
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 = context.Blogs
          .OrderByDescending(blog => blog.Rating)
          .Select(
              blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
          .ToList();
      
    • 在某些情況下,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 會傳回新的實例。