EF Core의 ID 확인

DbContext는 지정된 기본 키 값이 있는 인스턴스 하나만 추적할 수 있습니다. 즉, 키 값이 같은 엔터티의 여러 인스턴스를 단일 인스턴스로 확인해야 합니다. 이를 "ID 확인"이라고 합니다. ID 확인은 EF Core(Entity Framework Core)가 엔터티의 관계 또는 속성 값에 대한 모호성 없이 일관된 그래프를 추적하도록 합니다.

이 문서에서는 엔터티 상태와 EF Core 변경 내용 추적의 기본 사항을 이해한다고 가정합니다. 이러한 항목에 대한 자세한 내용은 EF Core의 변경 내용 추적을 참조하세요.

GitHub에서 샘플 코드를 다운로드하여 이 문서의 모든 코드를 실행하고 디버그할 수 있습니다.

소개

다음 코드는 엔터티를 쿼리한 다음 동일한 기본 키 값으로 다른 인스턴스를 연결하려고 시도합니다.

using var context = new BlogsContext();

var blogA = context.Blogs.Single(e => e.Id == 1);
var blogB = new Blog { Id = 1, Name = ".NET Blog (All new!)" };

try
{
    context.Update(blogB); // This will throw
}
catch (Exception e)
{
    Console.WriteLine($"{e.GetType().FullName}: {e.Message}");
}

코드를 실행하면 다음 예외가 발생합니다.

System.InvalidOperationException: 키 값이 '{Id: 1}'인 다른 인스턴스가 이미 추적되고 있으므로 엔터티 형식 'Blog'의 인스턴스를 추적할 수 없습니다. 기존 엔터티를 연결할 때 지정된 키 값이 있는 엔터티 인스턴스가 하나만 연결되어 있는지 확인합니다.

EF Core에는 다음과 같은 이유로 단일 인스턴스가 필요합니다.

  • 속성 값은 여러 인스턴스 간에 다를 수 있습니다. 데이터베이스를 업데이트할 때 EF Core는 사용할 속성 값을 알아야 합니다.
  • 다른 엔터티와의 관계는 여러 인스턴스 간에 다를 수 있습니다. 예를 들어 "blogA"는 "blogB"가 아닌 다른 게시물 모음과 관련이 있을 수 있습니다.

위의 예외는 일반적으로 다음과 같은 경우에 발생합니다.

  • 엔터티를 업데이트하려는 경우
  • 엔터티의 직렬화된 그래프를 추적하려는 경우
  • 자동으로 생성되지 않는 키 값을 설정하지 못한 경우
  • 여러 작업 단위에 DbContext 인스턴스를 다시 사용하는 경우

다음 섹션에서 이러한 각 상황에 대해 설명합니다.

엔터티 업데이트

EF Core의 변경 내용 추적엔터티 명시적 추적에 설명된 대로 엔터티를 새 값으로 업데이트하는 방법에는 여러 가지가 있습니다. 이러한 접근 방식은 ID 확인의 컨텍스트에서 아래에 설명되어 있습니다. 각 접근 방식은 쿼리나 Update 또는 Attach 중 하나에 대한 호출을 사용하지만 둘 다 사용하지는 않는다는 점에 유의해야 합니다.

호출 업데이트

업데이트할 엔터티가 SaveChanges에 사용할 DbContext의 쿼리에서 제공되지 않는 경우가 많습니다. 예를 들어 웹 애플리케이션에서는 POST 요청의 정보에서 엔터티 인스턴스를 만들 수 있습니다. 이 작업을 처리하는 가장 간단한 방법은 DbContext.Update 또는 DbSet<TEntity>.Update를 사용하는 것입니다. 예시:

public static void UpdateFromHttpPost1(Blog blog)
{
    using var context = new BlogsContext();

    context.Update(blog);

    context.SaveChanges();
}

이 경우 다음과 같습니다.

  • 엔터티의 단일 인스턴스만 만들어집니다.
  • 엔터티 인스턴스는 업데이트의 일부로 데이터베이스에서 쿼리되지 않습니다.
  • 모든 속성 값은 실제로 변경되었는지 여부에 관계없이 데이터베이스에서 업데이트됩니다.
  • 하나의 데이터베이스 왕복이 이루어집니다.

쿼리한 다음 변경 내용 적용

일반적으로 POST 요청 또는 이와 유사한 정보에서 엔터티를 만들 때 실제로 변경된 속성 값을 알 수 없습니다. 이전 예제에서와 같이 데이터베이스의 모든 값을 업데이트하는 것이 좋습니다. 그러나 애플리케이션이 많은 엔터티를 처리하고 소수의 엔터티만 실제 변경 내용이 있는 경우 전송된 업데이트를 제한하는 것이 유용할 수 있습니다. 이 작업은 현재 데이터베이스에 있는 엔터티를 추적하는 쿼리를 실행한 다음 이러한 추적된 엔터티에 변경 내용을 적용하여 수행할 수 있습니다. 예시:

public static void UpdateFromHttpPost2(Blog blog)
{
    using var context = new BlogsContext();

    var trackedBlog = context.Blogs.Find(blog.Id);

    trackedBlog.Name = blog.Name;
    trackedBlog.Summary = blog.Summary;

    context.SaveChanges();
}

이 경우 다음과 같습니다.

  • 엔터티의 단일 인스턴스, 즉 Find 쿼리에 의해 데이터베이스에서 반환되는 것만 추적됩니다.
  • Update, Attach 등은 사용되지 않습니다 .
  • 실제로 변경된 속성 값만 데이터베이스에서 업데이트됩니다.
  • 두 개의 데이터베이스 왕복이 이루어집니다.

EF Core에는 다음과 같은 속성 값을 전송하기 위한 몇 가지 도우미가 있습니다. 예를 들어 PropertyValues.SetValues는 지정된 개체의 모든 값을 복사하고 추적된 개체에 설정합니다.

public static void UpdateFromHttpPost3(Blog blog)
{
    using var context = new BlogsContext();

    var trackedBlog = context.Blogs.Find(blog.Id);

    context.Entry(trackedBlog).CurrentValues.SetValues(blog);

    context.SaveChanges();
}

SetValues는 엔터티 형식의 속성과 일치하는 속성 이름을 가진 DTO(데이터 전송 개체)를 비롯한 다양한 개체 형식을 허용합니다. 예시:

public static void UpdateFromHttpPost4(BlogDto dto)
{
    using var context = new BlogsContext();

    var trackedBlog = context.Blogs.Find(dto.Id);

    context.Entry(trackedBlog).CurrentValues.SetValues(dto);

    context.SaveChanges();
}

또는 속성 값에 대한 이름/값 항목이 있는 사전:

public static void UpdateFromHttpPost5(Dictionary<string, object> propertyValues)
{
    using var context = new BlogsContext();

    var trackedBlog = context.Blogs.Find(propertyValues["Id"]);

    context.Entry(trackedBlog).CurrentValues.SetValues(propertyValues);

    context.SaveChanges();
}

이와 같은 속성 값 작업에 대한 자세한 내용은 추적된 엔터티 액세스를 참조하세요.

원래 값 사용

지금까지 각 접근 방식은 업데이트하기 전에 쿼리를 실행했거나 변경 여부에 관계없이 모든 속성 값을 업데이트했습니다. 업데이트의 일부로 쿼리하지 않고 변경된 값만 업데이트하려면 변경된 속성 값에 대한 특정 정보가 필요합니다. 이 정보를 가져오는 일반적인 방법은 HTTP Post 또는 이와 유사한 현재 값과 원래 값을 모두 다시 보내는 것입니다. 예시:

public static void UpdateFromHttpPost6(Blog blog, Dictionary<string, object> originalValues)
{
    using var context = new BlogsContext();

    context.Attach(blog);
    context.Entry(blog).OriginalValues.SetValues(originalValues);

    context.SaveChanges();
}

이 코드에서는 수정된 값이 있는 엔터티가 먼저 연결됩니다. 이로 인해 EF Core는 Unchanged 상태의 엔터티를 추적합니다. 즉, 수정된 것으로 표시된 속성 값이 없습니다. 그런 다음 원래 값의 사전이 추적된 이 엔터티에 적용됩니다. 이렇게 하면 현재 값과 원래 값이 다른 수정된 속성으로 표시됩니다. 현재 값과 원래 값이 동일한 속성은 수정된 것으로 표시되지 않습니다.

이 경우 다음과 같습니다.

  • 연결을 사용하여 엔터티의 단일 인스턴스만 추적됩니다.
  • 엔터티 인스턴스는 업데이트의 일부로 데이터베이스에서 쿼리되지 않습니다.
  • 원래 값을 적용하면 실제로 변경된 속성 값만 데이터베이스에서 업데이트됩니다.
  • 하나의 데이터베이스 왕복이 이루어집니다.

이전 섹션의 예제와 마찬가지로 원래 값을 사전으로 전달할 필요는 없습니다. 엔터티 인스턴스 또는 DTO도 작동합니다.

이 방법은 매력적인 특성을 가지고 있지만 엔터티의 원래 값을 웹 클라이언트와 주고 받아야 합니다. 이 추가 복잡성이 이점의 가치가 있는지 신중하게 고려합니다. 많은 애플리케이션의 경우 더 간단한 방법 중 하나가 더 실용적입니다.

직렬화된 그래프 연결

EF Core는 외래 키 및 탐색 변경에 설명된 대로 외래 키 및 탐색 속성을 통해 연결된 엔터티 그래프와 함께 작동합니다. 예를 들어 JSON 파일을 사용하여 EF Core 외부에서 이러한 그래프를 만드는 경우 동일한 엔터티의 여러 인스턴스를 가질 수 있습니다. 그래프를 추적하려면 이러한 중복 항목을 단일 인스턴스로 확인해야 합니다.

중복이 없는 그래프

더 나아가기 전에 다음을 인식하는 것이 중요합니다.

  • 직렬 변환기에는 그래프에서 루프 및 중복 인스턴스를 처리하는 옵션이 있는 경우가 많습니다.
  • 그래프 루트로 사용되는 개체를 선택하면 중복 항목을 줄이거나 제거하는 데 도움이 되는 경우가 많습니다.

가능하면 serialization 옵션을 사용하고 중복되지 않는 루트를 선택합니다. 예를 들어 다음 코드는 Json.NET을 사용하여 각 블로그 목록을 연결된 게시물과 함께 직렬화합니다.

using var context = new BlogsContext();

var blogs = context.Blogs.Include(e => e.Posts).ToList();

var serialized = JsonConvert.SerializeObject(
    blogs,
    new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, Formatting = Formatting.Indented });

Console.WriteLine(serialized);

이 코드에서 생성된 JSON은 다음과 같습니다.

[
  {
    "Id": 1,
    "Name": ".NET Blog",
    "Summary": "Posts about .NET",
    "Posts": [
      {
        "Id": 1,
        "Title": "Announcing the Release of EF Core 5.0",
        "Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
        "BlogId": 1
      },
      {
        "Id": 2,
        "Title": "Announcing F# 5",
        "Content": "F# 5 is the latest version of F#, the functional programming language...",
        "BlogId": 1
      }
    ]
  },
  {
    "Id": 2,
    "Name": "Visual Studio Blog",
    "Summary": "Posts about Visual Studio",
    "Posts": [
      {
        "Id": 3,
        "Title": "Disassembly improvements for optimized managed debugging",
        "Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
        "BlogId": 2
      },
      {
        "Id": 4,
        "Title": "Database Profiling with Visual Studio",
        "Content": "Examine when database queries were executed and measure how long the take using...",
        "BlogId": 2
      }
    ]
  }
]

JSON에는 중복된 블로그 또는 게시물이 없습니다. 즉, Update에 대한 간단한 호출을 통해 데이터베이스에서 이러한 엔터티가 업데이트됩니다.

public static void UpdateBlogsFromJson(string json)
{
    using var context = new BlogsContext();

    var blogs = JsonConvert.DeserializeObject<List<Blog>>(json);

    foreach (var blog in blogs)
    {
        context.Update(blog);
    }

    context.SaveChanges();
}

중복 처리

이전 예제의 코드는 연결된 게시물을 사용하여 각 블로그를 직렬화했습니다. 연결된 블로그를 사용하여 각 게시물을 직렬화하도록 변경된 경우 중복 항목이 직렬화된 JSON에 도입됩니다. 예시:

using var context = new BlogsContext();

var posts = context.Posts.Include(e => e.Blog).ToList();

var serialized = JsonConvert.SerializeObject(
    posts,
    new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, Formatting = Formatting.Indented });

Console.WriteLine(serialized);

이제 직렬화된 JSON은 다음과 같습니다.

[
  {
    "Id": 1,
    "Title": "Announcing the Release of EF Core 5.0",
    "Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
    "BlogId": 1,
    "Blog": {
      "Id": 1,
      "Name": ".NET Blog",
      "Summary": "Posts about .NET",
      "Posts": [
        {
          "Id": 2,
          "Title": "Announcing F# 5",
          "Content": "F# 5 is the latest version of F#, the functional programming language...",
          "BlogId": 1
        }
      ]
    }
  },
  {
    "Id": 2,
    "Title": "Announcing F# 5",
    "Content": "F# 5 is the latest version of F#, the functional programming language...",
    "BlogId": 1,
    "Blog": {
      "Id": 1,
      "Name": ".NET Blog",
      "Summary": "Posts about .NET",
      "Posts": [
        {
          "Id": 1,
          "Title": "Announcing the Release of EF Core 5.0",
          "Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
          "BlogId": 1
        }
      ]
    }
  },
  {
    "Id": 3,
    "Title": "Disassembly improvements for optimized managed debugging",
    "Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
    "BlogId": 2,
    "Blog": {
      "Id": 2,
      "Name": "Visual Studio Blog",
      "Summary": "Posts about Visual Studio",
      "Posts": [
        {
          "Id": 4,
          "Title": "Database Profiling with Visual Studio",
          "Content": "Examine when database queries were executed and measure how long the take using...",
          "BlogId": 2
        }
      ]
    }
  },
  {
    "Id": 4,
    "Title": "Database Profiling with Visual Studio",
    "Content": "Examine when database queries were executed and measure how long the take using...",
    "BlogId": 2,
    "Blog": {
      "Id": 2,
      "Name": "Visual Studio Blog",
      "Summary": "Posts about Visual Studio",
      "Posts": [
        {
          "Id": 3,
          "Title": "Disassembly improvements for optimized managed debugging",
          "Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
          "BlogId": 2
        }
      ]
    }
  }
]

이제 그래프에 동일한 키 값이 있는 여러 블로그 인스턴스와 동일한 키 값을 가진 여러 Post 인스턴스가 포함됩니다. 이전 예제에서와 같이 이 그래프를 추적하려고 하면 다음이 throw됩니다.

System.InvalidOperationException: 키 값이 '{Id: 2}'인 다른 인스턴스가 이미 추적되고 있으므로 엔터티 형식 'Post'의 인스턴스를 추적할 수 없습니다. 기존 엔터티를 연결할 때 지정된 키 값이 있는 엔터티 인스턴스가 하나만 연결되어 있는지 확인합니다.

다음 두 가지 방법으로 이 문제를 해결할 수 있습니다.

  • 참조를 유지하는 JSON serialization 옵션 사용
  • 그래프를 추적하는 동안 ID 확인 수행

참조 유지

Json.NET은 이를 처리하는 PreserveReferencesHandling 옵션을 제공합니다. 예시:

var serialized = JsonConvert.SerializeObject(
    posts,
    new JsonSerializerSettings
    {
        PreserveReferencesHandling = PreserveReferencesHandling.All, Formatting = Formatting.Indented
    });

그 결과로 얻게 되는 JSON은 다음과 같습니다.

{
  "$id": "1",
  "$values": [
    {
      "$id": "2",
      "Id": 1,
      "Title": "Announcing the Release of EF Core 5.0",
      "Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
      "BlogId": 1,
      "Blog": {
        "$id": "3",
        "Id": 1,
        "Name": ".NET Blog",
        "Summary": "Posts about .NET",
        "Posts": [
          {
            "$ref": "2"
          },
          {
            "$id": "4",
            "Id": 2,
            "Title": "Announcing F# 5",
            "Content": "F# 5 is the latest version of F#, the functional programming language...",
            "BlogId": 1,
            "Blog": {
              "$ref": "3"
            }
          }
        ]
      }
    },
    {
      "$ref": "4"
    },
    {
      "$id": "5",
      "Id": 3,
      "Title": "Disassembly improvements for optimized managed debugging",
      "Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
      "BlogId": 2,
      "Blog": {
        "$id": "6",
        "Id": 2,
        "Name": "Visual Studio Blog",
        "Summary": "Posts about Visual Studio",
        "Posts": [
          {
            "$ref": "5"
          },
          {
            "$id": "7",
            "Id": 4,
            "Title": "Database Profiling with Visual Studio",
            "Content": "Examine when database queries were executed and measure how long the take using...",
            "BlogId": 2,
            "Blog": {
              "$ref": "6"
            }
          }
        ]
      }
    },
    {
      "$ref": "7"
    }
  ]
}

이 JSON은 그래프에서 이미 존재하는 인스턴스를 참조하는 "$ref": "5"와 같은 참조로 중복 항목을 대체했습니다. 이 그래프는 위에 표시된 것처럼 Update에 대한 간단한 호출을사용하여 다시 추적할 수 있습니다.

.NET BCL(기본 클래스 라이브러리)의 System.Text.Json 지원에는 동일한 결과를 생성하는 유사한 옵션이 있습니다. 예시:

var serialized = JsonSerializer.Serialize(
    posts, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve, WriteIndented = true });

중복 항목 해결

serialization 프로세스에서 중복 항목을 제거할 수 없는 경우 ChangeTracker.TrackGraph는 이를 처리하는 방법을 제공합니다. TrackGraph는 추적하기 전에 모든 엔터티 인스턴스에 대한 콜백을 생성한다는 점을 제외하면 Add, Attach, Update와 같이 작동합니다. 이 콜백은 엔터티를 추적하거나 무시하는 데 사용할 수 있습니다. 예시:

public static void UpdatePostsFromJsonWithIdentityResolution(string json)
{
    using var context = new BlogsContext();

    var posts = JsonConvert.DeserializeObject<List<Post>>(json);

    foreach (var post in posts)
    {
        context.ChangeTracker.TrackGraph(
            post, node =>
            {
                var keyValue = node.Entry.Property("Id").CurrentValue;
                var entityType = node.Entry.Metadata;

                var existingEntity = node.Entry.Context.ChangeTracker.Entries()
                    .FirstOrDefault(
                        e => Equals(e.Metadata, entityType)
                             && Equals(e.Property("Id").CurrentValue, keyValue));

                if (existingEntity == null)
                {
                    Console.WriteLine($"Tracking {entityType.DisplayName()} entity with key value {keyValue}");

                    node.Entry.State = EntityState.Modified;
                }
                else
                {
                    Console.WriteLine($"Discarding duplicate {entityType.DisplayName()} entity with key value {keyValue}");
                }
            });
    }

    context.SaveChanges();
}

그래프의 각 엔터티에 대해 이 코드는 다음을 수행합니다.

  • 엔터티의 엔터티 형식 및 키 값 찾기
  • 변경 추적기에서 이 키를 사용하여 엔터티 조회
    • 엔터티가 발견되면 엔터티가 중복되므로 추가 작업이 수행되지 않습니다.
    • 엔터티를 찾을 수 없는 경우 상태를 Modified로 설정하여 추적합니다.

이 코드를 실행하는 출력은 다음과 같습니다.

Tracking EntityType: Post entity with key value 1
Tracking EntityType: Blog entity with key value 1
Tracking EntityType: Post entity with key value 2
Discarding duplicate EntityType: Post entity with key value 2
Tracking EntityType: Post entity with key value 3
Tracking EntityType: Blog entity with key value 2
Tracking EntityType: Post entity with key value 4
Discarding duplicate EntityType: Post entity with key value 4

Important

이 코드 은 모든 중복 항목이 동일하다고 가정합니다. 이렇게 하면 다른 복제본을 삭제하는 동안 추적할 중복 항목 중 하나를 임의로 선택할 수 있습니다. 중복 항목이 다를 수 있는 경우 코드는 사용할 항목을 결정하는 방법과 속성 및 탐색 값을 함께 결합하는 방법을 결정해야 합니다.

참고 항목

간단히 하기 위해 이 코드는 각 엔터티에 Id라는 기본 키 속성이 있다고 가정합니다. 추상 기본 클래스 또는 인터페이스로 코딩할 수 있습니다. 또는 이 코드가 모든 형식의 엔터티에서 작동할 수 있도록 IEntityType 메타데이터에서 기본 키 속성 또는 속성을 가져올 수 있습니다.

키 값을 설정하지 못했습니다.

엔터티 형식은 자동으로 생성된 키 값을 사용하도록 구성되는 경우가 많습니다. 이는 비 복합 키의 정수 및 GUID 속성에 대한 기본값입니다. 그러나 엔터티 형식이 자동으로 생성된 키 값을 사용하도록 구성되지 않은 경우 엔터티를 추적하기 전에 명시적 키 값을 설정해야 합니다. 예를 들어 다음 엔터티 형식을 사용하세요.

public class Pet
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }

    public string Name { get; set; }
}

키 값을 설정하지 않고 두 개의 새 엔터티 인스턴스를 추적하려는 코드를 고려합니다.

using var context = new BlogsContext();

context.Add(new Pet { Name = "Smokey" });

try
{
    context.Add(new Pet { Name = "Clippy" }); // This will throw
}
catch (Exception e)
{
    Console.WriteLine($"{e.GetType().FullName}: {e.Message}");
}

이 코드는 다음을 throw합니다.

System.InvalidOperationException: 키 값이 '{Id: 0}'인 다른 인스턴스가 이미 추적되고 있으므로 엔터티 형식 'Pet'의 인스턴스를 추적할 수 없습니다. 기존 엔터티를 연결할 때 지정된 키 값이 있는 엔터티 인스턴스가 하나만 연결되어 있는지 확인합니다.

이에 대한 수정 사항은 키 값을 명시적으로 설정하거나 생성된 키 값을 사용하도록 키 속성을 구성하는 것입니다. 자세한 내용은 생성된 값을 참조하세요.

단일 DbContext 인스턴스를 과도하게 사용

DbContextDbContext 초기화 및 구성에 설명된 대로 수명이 짧은 작업 단위를 나타내도록 설계되었으며 EF Core의 변경 내용 추적에 자세히 설명되어 있습니다. 이 지침을 따르지 않으면 동일한 엔터티의 여러 인스턴스를 추적하려고 시도하는 상황이 쉽게 발생할 수 있습니다. 일반적인 예는 다음과 같습니다.

  • 동일한 DbContext 인스턴스를 사용하여 테스트 상태를 설정한 다음 테스트를 실행합니다. 이로 인해 DbContext는 테스트 설정에서 하나의 엔터티 인스턴스를 추적한 다음 테스트에 새 인스턴스를 제대로 연결하려고 시도하는 경우가 많습니다. 대신 테스트 상태 및 테스트 코드를 적절하게 설정하기 위해 다른 DbContext 인스턴스를 사용합니다.
  • 리포지토리 또는 유사한 코드에서 공유 DbContext 인스턴스 사용. 대신 리포지토리가 각 작업 단위에 대해 단일 DbContext 인스턴스를 사용하는지 확인합니다.

ID 확인 및 쿼리

ID 확인은 엔터티가 쿼리에서 추적될 때 자동으로 발생합니다. 즉, 지정된 키 값이 있는 엔터티 인스턴스가 이미 추적된 경우 이 기존 추적된 인스턴스는 새 인스턴스를 만드는 대신 사용됩니다. 이는 중요한 결과를 초래합니다. 데이터베이스에서 데이터가 변경된 경우 쿼리 결과에 반영되지 않습니다. 이는 DbContext 초기화 및 구성에 설명된 대로 각 작업 단위에 대해 새 DbContext 인스턴스를 사용할 좋은 이유이며 EF Core의 변경 내용 추적에서 이에 대해 자세히 설명합니다.

Important

EF Core는 항상 데이터베이스에 대해 DbSet에서 LINQ 쿼리를 실행하고 데이터베이스에 있는 내용에 따라 결과만 반환한다는 것을 이해하는 것이 중요합니다. 그러나 추적 쿼리의 경우 반환된 엔터티가 이미 추적된 경우 추적된 인스턴스는 데이터베이스의 데이터에서 인스턴스를 만드는 대신 사용됩니다.

Reload() 또는 GetDatabaseValues()는 추적된 엔터티를 데이터베이스의 최신 데이터로 새로 고쳐야 하는 경우에 사용할 수 있습니다. 자세한 내용은 추적된 엔터티 액세스를 참조하세요.

추적 쿼리와 달리 추적되지 않는 쿼리는 ID 확인을 수행하지 않습니다. 즉, 추적 없음 쿼리는 앞에서 설명한 JSON serialization 사례와 마찬가지로 중복 항목을 반환할 수 있습니다. 쿼리 결과가 직렬화되어 클라이언트로 전송되는 경우에는 일반적으로 문제가 되지 않습니다.

추적 금지 쿼리를 정기적으로 수행한 다음 반환된 엔터티를 동일한 컨텍스트에 연결하지 마세요. 추적 쿼리를 사용하는 것보다 속도가 느리고 제대로 되기가 더 어렵습니다.

추적 금지 쿼리는 ID 확인을 수행하지 않습니다. 이렇게 하면 쿼리에서 많은 수의 엔터티를 스트리밍하는 성능에 영향을 주므로 ID 확인이 수행되지 않습니다. ID 확인은 나중에 중복을 만드는 대신 사용할 수 있도록 반환된 각 인스턴스를 추적해야 하기 때문입니다.

추적 없음 쿼리는 AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>)를 사용하여 ID 확인을 강제로 수행할 수 있습니다. 그런 다음, 쿼리는 반환된 인스턴스를 정상적인 방식으로 추적하지 않고 추적하며 쿼리 결과에 중복이 생성되지 않도록 합니다.

개체 같음 재정의

EF Core는 엔터티 인스턴스를 비교할 때 참조 같음을 사용합니다. 엔터티 형식이 Object.Equals(Object)를 재정의하거나 개체 같음을 변경하지 않는 경우에도 마찬가지입니다. 그러나 같음을 재정의하면 EF Core 동작에 영향을 미칠 수 있는 한 가지 위치가 있습니다. 컬렉션 탐색에서 참조 같음 대신 재정의된 같음을 사용하므로 여러 인스턴스를 동일하게 보고합니다.

이 때문에 엔터티 같음 재정의를 피해야 합니다. 이 탐색을 사용하는 경우 참조 같음을 강제로 적용하는 컬렉션 탐색을 만들어야 합니다. 예를 들어 참조 같음을 사용하는 같음 비교자를 만듭니다.

public sealed class ReferenceEqualityComparer : IEqualityComparer<object>
{
    private ReferenceEqualityComparer()
    {
    }

    public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer();

    bool IEqualityComparer<object>.Equals(object x, object y) => x == y;

    int IEqualityComparer<object>.GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
}

(.NET 5부터 BCL에 ReferenceEqualityComparer로 포함됩니다.)

그런 다음 컬렉션 탐색을 만들 때 이 비교자를 사용할 수 있습니다. 예시:

public ICollection<Order> Orders { get; set; }
    = new HashSet<Order>(ReferenceEqualityComparer.Instance);

키 속성 비교

같음 비교 외에도 키 값을 정렬해야 합니다. 이는 SaveChanges에 대한 단일 호출에서 여러 엔터티를 업데이트할 때 교착 상태를 방지하는 데 중요합니다. 기본, 대체 또는 외래 키 속성에 사용되는 모든 형식과 고유 인덱스에 사용되는 형식은 IComparable<T>IEquatable<T>을 구현해야 합니다. 일반적으로 키로 사용되는 형식(int, Guid, 문자열 등)은 이미 이러한 인터페이스를 지원합니다. 사용자 지정 키 형식은 이러한 인터페이스를 추가할 수 있습니다.