추적 및 비 추적 쿼리

추적 동작은 Entity Framework Core가 해당 변경 추적기에서 엔터티 인스턴스에 대한 정보를 유지할지 여부를 제어합니다. 엔터티를 추적하는 경우 엔터티에서 검색된 변경 내용은 SaveChanges 중 데이터베이스에 유지됩니다. EF Core는 추적 쿼리 결과에 있는 엔터티와 변경 추적기에 있는 엔터티 간 탐색 속성도 수정합니다.

참고 항목

키 없는 엔터티 형식은 추적되지 않습니다. 이 문서에서 언급되는 모든 엔터티 형식은 키가 정의된 엔터티 형식을 가리킵니다.

GitHub에서 이 문서의 샘플을 볼 수 있습니다.

추적 쿼리

기본적으로 엔터티 형식을 반환하는 쿼리는 추적됩니다. 추적 쿼리는 엔터티 인스턴스에 대한 변경 내용이 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();

다음 섹션에서는 비 추적 쿼리가 추적 쿼리보다 덜 효율적일 수 있는 경우를 설명합니다.

ID 확인

추적 쿼리는 변경 추적기를 사용하므로 EF Core는 추적 쿼리에서 ID 확인을 수행합니다. 엔터티를 구체화할 때 엔터티 인스턴스가 이미 추적 중인 경우 EF Core는 변경 추적기에서 동일한 엔터티 인스턴스를 반환합니다. 결과에 동일한 엔터티가 여러 번 포함되는 경우 발생할 때마다 동일한 인스턴스를 다시 가져옵니다. 비 추적 쿼리

  • 변경 추적기를 사용하지 않으며 ID 확인을 수행하지 않습니다.
  • 동일한 엔터티가 결과에 여러 번 포함되는 경우에도 엔터티의 새 인스턴스를 다시 가져옵니다.

추적과 비 추적은 동일한 쿼리로 결합할 수 있습니다. 즉, 결과에서 ID 확인을 수행하는 비 추적 쿼리를 사용할 수 있습니다. AsNoTracking 쿼리 가능 연산자와 마찬가지로 또 다른 연산자 AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>)을 추가했습니다. QueryTrackingBehavior 열거형에 추가된 관련 항목도 있습니다. 비 추적으로 ID 확인을 사용하도록 쿼리를 구성하는 경우 쿼리 결과를 생성할 때 백그라운드에서 독립 실행형 변경 추적기를 사용하므로 각 인스턴스가 한 번만 구체화됩니다. 이 변경 추적기는 컨텍스트의 추적기와 다르므로 컨텍스트를 통해 결과가 추적되지 않습니다. 쿼리가 완전히 열거된 후에는 변경 추적기가 범위를 벗어나고 필요에 따라 가비지가 수집됩니다.

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가 클라이언트 평가를 위해 엔터티 인스턴스를 구체화하는 경우 이 엔터티 인스턴스가 추적됩니다. 여기서는 클라이언트 메서드 StandardizeURLblog 엔터티를 전달하므로 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의 추적 수행 방법에 약간의 차이가 있었습니다. 주목할 만한 차이점은 다음과 같습니다.

  • 클라이언트 및 서버 평가 페이지에 설명한 것처럼 버전 3.0 이전에는 EF Core가 쿼리의 모든 부분에서 클라이언트 평가를 지원했습니다. 클라이언트 평가로 인해 결과에 포함되지 않는 엔터티의 구체화가 수행되었습니다. 따라서 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는 비 추적 쿼리에서 ID 확인을 수행했습니다. EF Core는 약한 참조를 사용하여 이미 반환된 엔터티를 추적했습니다. 따라서 결과 집합에 동일한 엔터티가 여러 번 포함된 경우 발생할 때마다 동일한 인스턴스를 가져옵니다. ID가 동일한 이전 결과가 범위를 벗어나고 가비지가 수집된 경우에도 EF Core는 새 인스턴스를 반환했습니다.