トラッキング vs. No-Tracking クエリ

追跡動作は、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 は追跡クエリで ID 解決を行います。 エンティティを具体化する場合、EF Core は変更トラッカーから同じエンティティ インスタンスを返します (既に追跡されている場合)。 結果に同じエンティティが複数回含まれている場合は、出現するたびに同じインスタンスが返されます。 追跡なしクエリ:

  • 変更トラッカーを使用せず、ID 解決を行わないでください。
  • 同じエンティティが結果に複数回含まれている場合でも、エンティティの新しいインスタンスを返します。

追跡と追跡なしは、同じクエリで組み合わせることができます。 つまり、追跡なしのクエリを使用して、結果の ID 解決を行うことができます。 クエリ可能な演算子 AsNoTracking 同様に、別の演算子 AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>)を追加しました。 QueryTrackingBehavior列挙型には、関連するエントリも追加されています。 ID 解決を使用するクエリが追跡なしで構成されている場合、クエリ結果を生成するときにスタンドアロンの変更トラッカーがバックグラウンドで使用されるため、各インスタンスは 1 回だけ具体化されます。 この変更トラッカーはコンテキスト内の変更トラッカーとは異なるため、結果はコンテキストによって追跡されません。 クエリが完全に列挙されると、変更トラッカーはスコープ外になり、必要に応じてガベージ コレクションされます。

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 は、追跡なしのクエリで ID 解決を行うために使用されます。 弱い参照を使用して、既に返されていたエンティティを追跡していました。 そのため、結果セットに同じエンティティが複数回含まれている場合は、出現するたびに同じインスタンスが取得されます。 ただし、同じ識別子を持つ以前の結果が範囲外になり、ガベージコレクションが発生した場合、EF Coreは新しいインスタンスを生成して返します。