추적된 엔터티 액세스
DbContext에 의해 추적되는 엔터티에 액세스하는 데는 네 가지 주요 API가 있습니다.
- DbContext.Entry는 지정된 엔터티 인스턴스에 대해 EntityEntry<TEntity> 인스턴스를 반환합니다.
- ChangeTracker.Entries는 추적된 모든 엔터티 또는 지정된 형식의 추적된 모든 엔터티에 대해 EntityEntry<TEntity> 인스턴스를 반환합니다.
- DbContext.Find, DbContext.FindAsync, DbSet<TEntity>.Find, DbSet<TEntity>.FindAsync는 기본 키로 단일 엔터티를 찾은 다음, 먼저 추적된 엔터티를 찾은 다음 필요한 경우 데이터베이스를 쿼리합니다.
- DbSet<TEntity>.Local는 DbSet이 나타내는 엔터티 형식의 엔터티에 대한 실제 엔터티(EntityEntry 인스턴스 아님)를 반환합니다.
각 변경에 대한 자세한 설명은 아래 섹션에 자세히 나와 있습니다.
팁
이 문서에서는 엔터티 상태와 EF Core 변경 내용 추적의 기본 사항을 이해한다고 가정합니다. 이러한 항목에 대한 자세한 내용은 EF Core의 변경 내용 추적을 참조하세요.
팁
GitHub에서 샘플 코드를 다운로드하여 이 문서의 모든 코드를 실행하고 디버그할 수 있습니다.
DbContext.Entry 및 EntityEntry 인스턴스 사용
추적된 각 엔터티에 대해 EF Core(Entity Framework Core)는 다음을 추적합니다.
- 엔터티의 전체 상태입니다.
Unchanged
,Modified
,Added
또는Deleted
중 하나입니다. 자세한 내용은 EF Core의 변경 내용 추적을 참조하세요. - 추적된 엔터티 간의 관계입니다. 예를 들어 게시물이 속한 블로그입니다.
- 속성의 "현재 값"입니다.
- 이 정보를 사용할 수 있는 경우 속성의 "원래 값"입니다. 원래 값은 데이터베이스에서 엔터티를 쿼리할 때 존재했던 속성 값입니다.
- 쿼리된 이후 수정된 속성 값입니다.
- 값이 임시인지 여부와 같은 속성 값에 대한 기타 정보입니다.
엔터티 인스턴스를 DbContext.Entry에 전달하면 EntityEntry<TEntity>는 지정된 엔터티에 대한 이 정보에 대한 액세스 권한을 제공합니다. 예시:
using var context = new BlogsContext();
var blog = context.Blogs.Single(e => e.Id == 1);
var entityEntry = context.Entry(blog);
다음 섹션에서는 EntityEntry를 사용하여 엔터티 상태와 엔터티의 속성 및 탐색 상태에 액세스하고 조작하는 방법을 보여 줍니다.
엔터티 작업
EntityEntry<TEntity>의 가장 일반적인 용도는 엔터티의 현재 EntityState에 액세스하는 것입니다. 예시:
var currentState = context.Entry(blog).State;
if (currentState == EntityState.Unchanged)
{
context.Entry(blog).State = EntityState.Modified;
}
Entry 메서드는 아직 추적되지 않은 엔터티에서도 사용할 수 있습니다. 이렇게 하면 엔터티 추적이 시작되지 않습니다. 엔터티의 상태는 여전히 Detached
입니다. 그러나 반환된 EntityEntry를 사용하여 엔터티 상태를 변경할 수 있습니다. 이때 엔터티는 지정된 상태에서 추적됩니다. 예를 들어 다음 코드는 블로그 인스턴스를 Added
로 추적하기 시작합니다.
var newBlog = new Blog();
Debug.Assert(context.Entry(newBlog).State == EntityState.Detached);
context.Entry(newBlog).State = EntityState.Added;
Debug.Assert(context.Entry(newBlog).State == EntityState.Added);
팁
EF6과 달리 개별 엔터티의 상태를 설정해도 연결된 모든 엔터티가 추적되지는 않습니다. 이렇게 하면 엔터티의 전체 그래프에서 작동하는 Add
, Attach
또는 Update
를 호출하는 것보다 이러한 방식으로 상태를 낮은 수준의 작업으로 설정합니다.
다음 표에서는 EntityEntry를 사용하여 전체 엔터티로 작업하는 방법을 요약합니다.
EntityEntry 멤버 | 설명 |
---|---|
EntityEntry.State | 엔터티의 EntityState를 가져오고 설정합니다. |
EntityEntry.Entity | 엔터티 인스턴스를 가져옵니다. |
EntityEntry.Context | 이 엔터티를 추적하는 DbContext입니다. |
EntityEntry.Metadata | 엔터티 형식에 대한 IEntityType 메타데이터입니다. |
EntityEntry.IsKeySet | 엔터티에 키 값이 설정되었는지 여부입니다. |
EntityEntry.Reload() | 데이터베이스에서 읽은 값으로 속성 값을 덮어씁니다. |
EntityEntry.DetectChanges() | 이 엔터티에 대한 변경 내용만 강제로 검색합니다. 변경 검색 및 알림을 참조하세요. |
단일 속성 작업
EntityEntry<TEntity>.Property의 여러 오버로드를 통해 엔터티의 개별 속성에 대한 정보에 액세스할 수 있습니다. 예를 들어 강력한 형식의 흐름과 유사한 API를 사용합니다.
PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property(e => e.Name);
대신 속성 이름을 문자열로 전달할 수 있습니다. 예시:
PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property<string>("Name");
반환된 PropertyEntry<TEntity,TProperty>를 사용하여 속성에 대한 정보에 액세스할 수 있습니다. 예를 들어 이 엔터티에서 속성의 현재 값을 가져와서 설정하는 데 사용할 수 있습니다.
string currentValue = context.Entry(blog).Property(e => e.Name).CurrentValue;
context.Entry(blog).Property(e => e.Name).CurrentValue = "1unicorn2";
위에서 사용한 두 Property 메서드는 모두 강력한 형식의 제네릭 PropertyEntry<TEntity,TProperty> 인스턴스를 반환합니다. 이 제네릭 형식은 boxing 값 형식 없이 속성 값에 액세스할 수 있으므로 사용하는 것이 좋습니다. 그러나 컴파일 시간에 엔터티 또는 속성의 형식을 알 수 없는 경우 제네릭이 아닌 PropertyEntry를 대신 가져올 수 있습니다.
PropertyEntry propertyEntry = context.Entry(blog).Property("Name");
이렇게 하면 boxing 값 형식을 희생하여 형식에 관계없이 모든 속성에 대한 속성 정보에 액세스할 수 있습니다. 예시:
object blog = context.Blogs.Single(e => e.Id == 1);
object currentValue = context.Entry(blog).Property("Name").CurrentValue;
context.Entry(blog).Property("Name").CurrentValue = "1unicorn2";
다음 표에는 PropertyEntry에서 노출하는 속성 정보가 요약되어 있습니다.
PropertyEntry 멤버 | 설명 |
---|---|
PropertyEntry<TEntity,TProperty>.CurrentValue | 속성의 현재 값을 가져오거나 설정합니다. |
PropertyEntry<TEntity,TProperty>.OriginalValue | 사용 가능한 경우 속성의 원래 값을 가져오고 설정합니다. |
PropertyEntry<TEntity,TProperty>.EntityEntry | 엔터티에 대한 EntityEntry<TEntity>에 대한 백 참조입니다. |
PropertyEntry.Metadata | 속성의 IProperty 메타데이터입니다. |
PropertyEntry.IsModified | 이 속성이 수정된 것으로 표시되어 이 상태를 변경할 수 있는지 여부를 나타냅니다. |
PropertyEntry.IsTemporary | 이 속성이 임시로 표시되어 이 상태를 변경할 수 있는지 여부를 나타냅니다. |
참고:
- 속성의 원래 값은 데이터베이스에서 엔터티를 쿼리할 때 속성이 가진 값입니다. 그러나 엔터티의 연결이 끊긴 다음 다른 DbContext(예:
Attach
또는Update
)에 명시적으로 연결된 경우 원래 값을 사용할 수 없습니다. 이 경우 반환된 원래 값은 현재 값과 동일합니다. - SaveChanges는 수정된 것으로 표시된 속성만 업데이트합니다. EF Core가 지정된 속성 값을 강제로 업데이트하도록 하려면 IsModified를 true로 설정하거나 false로 설정하여 EF Core가 속성 값을 업데이트하지 못하도록 합니다.
- 임시 값 은 일반적으로 EF Core 값 생성기에서 생성됩니다. 속성의 현재 값을 설정하면 임시 값이 지정된 값으로 대체되고 속성을 임시 값이 아닌 것으로 표시합니다. 명시적으로 설정한 후에도 값을 임시로 설정하려면 IsTemporary를 true로 설정합니다.
단일 탐색 작업
EntityEntry<TEntity>.Reference, EntityEntry<TEntity>.Collection 및 EntityEntry.Navigation의 여러 오버로드는 개별 탐색에 대한 정보에 대한 액세스를 허용합니다.
단일 관련 엔터티에 대한 참조 탐색은 Reference 메서드를 통해 액세스됩니다. 참조 탐색은 일대다 관계의 "일" 측면과 일대일 관계의 양쪽을 가리킵니다. 예시:
ReferenceEntry<Post, Blog> referenceEntry1 = context.Entry(post).Reference(e => e.Blog);
ReferenceEntry<Post, Blog> referenceEntry2 = context.Entry(post).Reference<Blog>("Blog");
ReferenceEntry referenceEntry3 = context.Entry(post).Reference("Blog");
탐색은 일 대 다 및 다 대 다 관계의 "다" 측면에 사용되는 경우 관련 엔터티의 컬렉션일 수도 있습니다. Collection 메서드는 컬렉션 탐색에 액세스하는 데 사용됩니다. 예시:
CollectionEntry<Blog, Post> collectionEntry1 = context.Entry(blog).Collection(e => e.Posts);
CollectionEntry<Blog, Post> collectionEntry2 = context.Entry(blog).Collection<Post>("Posts");
CollectionEntry collectionEntry3 = context.Entry(blog).Collection("Posts");
일부 작업은 모든 탐색에 공통적으로 적용됩니다. EntityEntry.Navigation 메서드를 사용하여 참조 및 컬렉션 탐색 모두에 액세스할 수 있습니다. 모든 탐색에 함께 액세스할 때는 제네릭이 아닌 액세스만 사용할 수 있습니다. 예시:
NavigationEntry navigationEntry = context.Entry(blog).Navigation("Posts");
다음 표에는 ReferenceEntry<TEntity,TProperty>, CollectionEntry<TEntity,TRelatedEntity> 및 NavigationEntry를 사용하는 방법이 요약되어 있습니다.
NavigationEntry 멤버 | 설명 |
---|---|
MemberEntry.CurrentValue | 탐색 속성의 현재 값을 가져오거나 설정합니다. 컬렉션 탐색을 위한 전체 컬렉션입니다. |
NavigationEntry.Metadata | 탐색에 대한 INavigationBase 메타데이터입니다. |
NavigationEntry.IsLoaded | 관련 엔터티 또는 컬렉션이 데이터베이스에서 완전히 로드되었는지 여부를 나타내는 값을 가져오거나 설정합니다. |
NavigationEntry.Load() | 데이터베이스에서 관련 엔터티 또는 컬렉션을 로드합니다. 관련 데이터의 명시적 로드를 참조하세요. |
NavigationEntry.Query() | 쿼리 EF Core는 이 탐색을 추가로 구성할 수 있는 IQueryable 로 로드하는 데 사용합니다. 관련 데이터의 명시적 로드를 참조하세요. |
엔터티의 모든 속성 작업
EntityEntry.Properties는 엔터티의 모든 속성에 대해 PropertyEntry의 IEnumerable<T>을 반환합니다. 엔터티의 모든 속성에 대해 작업을 수행하는 데 사용할 수 있습니다. 예를 들어 DateTime 속성을 DateTime.Now
로 설정하려면 다음을 수행합니다.
foreach (var propertyEntry in context.Entry(blog).Properties)
{
if (propertyEntry.Metadata.ClrType == typeof(DateTime))
{
propertyEntry.CurrentValue = DateTime.Now;
}
}
또한 EntityEntry에는 모든 속성 값을 동시에 가져와서 설정하는 여러 메서드가 포함되어 있습니다. 이러한 메서드는 속성 및 해당 값의 컬렉션을 나타내는 PropertyValues 클래스를 사용합니다. PropertyValues는 현재 또는 원래 값 또는 현재 데이터베이스에 저장된 값에 대해 가져올 수 있습니다. 예시:
var currentValues = context.Entry(blog).CurrentValues;
var originalValues = context.Entry(blog).OriginalValues;
var databaseValues = context.Entry(blog).GetDatabaseValues();
이러한 PropertyValues 개체는 그 자체로는 별로 유용하지 않습니다. 그러나 엔터티를 조작할 때 필요한 일반적인 작업을 수행하기 위해 결합할 수 있습니다. 이는 데이터 전송 개체로 작업하고 낙관적 동시성 충돌을 해결할 때 유용합니다. 다음 섹션에서는 몇 가지 예를 보여 줍니다.
엔터티 또는 DTO에서 현재 또는 원래 값 설정
엔터티의 현재 또는 원래 값은 다른 개체에서 값을 복사하여 업데이트할 수 있습니다. 예를 들어 엔터티 형식과 동일한 속성을 가진 BlogDto
DTO(데이터 전송 개체)를 고려합니다.
public class BlogDto
{
public int Id { get; set; }
public string Name { get; set; }
}
PropertyValues.SetValues를 사용하여 추적된 엔터티의 현재 값을 설정하는 데 사용할 수 있습니다.
var blogDto = new BlogDto { Id = 1, Name = "1unicorn2" };
context.Entry(blog).CurrentValues.SetValues(blogDto);
이 기술은 서비스 호출 또는 n 계층 애플리케이션의 클라이언트에서 가져온 값으로 엔터티를 업데이트할 때도 사용됩니다. 이름이 엔터티의 속성과 일치하는 경우 사용되는 개체가 엔터티와 동일한 형식일 필요는 없습니다. 위의 예제에서는 DTO BlogDto
의 인스턴스를 사용하여 추적된 Blog
엔터티의 현재 값을 설정합니다.
속성은 값 집합이 현재 값과 다른 경우에만 수정된 것으로 표시됩니다.
사전에서 현재 또는 원래 값 설정
이전 예제에서는 엔터티 또는 DTO 인스턴스의 값을 설정합니다. 속성 값이 사전에 이름/값 쌍으로 저장되는 경우에도 동일한 동작을 사용할 수 있습니다. 예시:
var blogDictionary = new Dictionary<string, object> { ["Id"] = 1, ["Name"] = "1unicorn2" };
context.Entry(blog).CurrentValues.SetValues(blogDictionary);
데이터베이스에서 현재 또는 원래 값 설정
엔터티의 현재 또는 원래 값은 GetDatabaseValues() 또는 GetDatabaseValuesAsync를 호출하고 반환된 개체를 사용하여 현재 또는 원래 값 또는 둘 다를 설정하여 데이터베이스의 최신 값으로 업데이트할 수 있습니다. 예시:
var databaseValues = context.Entry(blog).GetDatabaseValues();
context.Entry(blog).CurrentValues.SetValues(databaseValues);
context.Entry(blog).OriginalValues.SetValues(databaseValues);
현재, 원본 또는 데이터베이스 값을 포함하는 복제된 개체 만들기
CurrentValues, OriginalValues 또는 GetDatabaseValues에서 반환된 PropertyValues 개체는 PropertyValues.ToObject()를 사용하여 엔터티의 복제본을 만드는 데 사용할 수 있습니다. 예시:
var clonedBlog = context.Entry(blog).GetDatabaseValues().ToObject();
ToObject
는 DbContext에서 추적되지 않는 새 인스턴스를 반환합니다. 반환된 개체에는 다른 엔터티로 설정된 관계도 없습니다.
복제된 개체는 특히 특정 형식의 개체에 데이터를 바인딩할 때 데이터베이스에 대한 동시 업데이트와 관련된 문제를 해결하는 데 유용할 수 있습니다. 자세한 내용은 낙관적 동시성을 참조하세요.
엔터티의 모든 탐색 작업
EntityEntry.Navigations는 엔터티의 모든 탐색에 대해 NavigationEntry의 IEnumerable<T>을 반환합니다. EntityEntry.References 및 EntityEntry.Collections는 동일한 작업을 수행하지만 각각 참조 또는 컬렉션 탐색으로 제한됩니다. 엔터티의 모든 탐색에 대해 작업을 수행하는 데 사용할 수 있습니다. 예를 들어 모든 관련 엔터티를 강제로 로드하려면 다음을 수행합니다.
foreach (var navigationEntry in context.Entry(blog).Navigations)
{
navigationEntry.Load();
}
엔터티의 모든 멤버 작업
일반 속성 및 탐색 속성은 상태와 동작이 다릅니다. 따라서 위의 섹션과 같이 탐색 및 비 탐색을 별도로 처리하는 것이 일반적입니다. 그러나 일반 속성인지 탐색인지에 관계없이 엔터티의 멤버로 작업을 수행하는 것이 유용할 수 있습니다. EntityEntry.Member 및 EntityEntry.Members는 이 목적을 위해 제공됩니다. 예시:
foreach (var memberEntry in context.Entry(blog).Members)
{
Console.WriteLine(
$"Member {memberEntry.Metadata.Name} is of type {memberEntry.Metadata.ClrType.ShortDisplayName()} and has value {memberEntry.CurrentValue}");
}
샘플에서 블로그에서 이 코드를 실행하면 다음 출력이 생성됩니다.
Member Id is of type int and has value 1
Member Name is of type string and has value .NET Blog
Member Posts is of type IList<Post> and has value System.Collections.Generic.List`1[Post]
팁
변경 추적기 디버그 보기에는 다음과 같은 정보가 표시됩니다. 전체 변경 추적기의 디버그 보기는 추적된 각 엔터티의 개별 EntityEntry.DebugView에서 생성됩니다.
찾기 및 FindAsync
DbContext.Find, DbContext.FindAsync, DbSet<TEntity>.Find 및 DbSet<TEntity>.FindAsync는 기본 키가 알려진 경우 단일 엔터티를 효율적으로 조회하도록 설계되었습니다. 엔터티가 이미 추적되었는지 먼저 확인하고, 이 경우 엔터티를 즉시 반환합니다. 데이터베이스 쿼리는 엔터티가 로컬로 추적되지 않는 경우에만 만들어집니다. 예를 들어 동일한 엔터티에 대해 Find를 두 번 호출하는 이 코드를 고려합니다.
using var context = new BlogsContext();
Console.WriteLine("First call to Find...");
var blog1 = context.Blogs.Find(1);
Console.WriteLine($"...found blog {blog1.Name}");
Console.WriteLine();
Console.WriteLine("Second call to Find...");
var blog2 = context.Blogs.Find(1);
Debug.Assert(blog1 == blog2);
Console.WriteLine("...returned the same instance without executing a query.");
SQLite를 사용할 때 이 코드의 출력(EF Core 로깅 포함)은 다음과 같습니다.
First call to Find...
info: 12/29/2020 07:45:53.682 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (1ms) [Parameters=[@__p_0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
SELECT "b"."Id", "b"."Name"
FROM "Blogs" AS "b"
WHERE "b"."Id" = @__p_0
LIMIT 1
...found blog .NET Blog
Second call to Find...
...returned the same instance without executing a query.
첫 번째 호출은 엔터티를 로컬로 찾을 수 없으므로 데이터베이스 쿼리를 실행합니다. 반대로 두 번째 호출은 이미 추적되고 있으므로 데이터베이스를 쿼리하지 않고 동일한 인스턴스를 반환합니다.
지정된 키를 가진 엔터티가 로컬로 추적되지 않고 데이터베이스에 없는 경우 Find는 null을 반환합니다.
복합 키
찾기는 복합 키와 함께 사용할 수도 있습니다. 예를 들어 주문 ID 및 제품 ID로 구성된 복합 키가 있는 OrderLine
엔터티를 고려합니다.
public class OrderLine
{
public int OrderId { get; set; }
public int ProductId { get; set; }
//...
}
복합 키는 DbContext.OnModelCreating에서 구성하여 키 부분 및 순서를 정의해야 합니다. 예시:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<OrderLine>()
.HasKey(e => new { e.OrderId, e.ProductId });
}
OrderId
는 키의 첫 번째 부분이며 ProductId
는 키의 두 번째 부분입니다. 이 순서는 찾기에 키 값을 전달할 때 사용해야 합니다. 예시:
var orderline = context.OrderLines.Find(orderId, productId);
ChangeTracker.Entries를 사용하여 추적된 모든 엔터티에 액세스
지금까지 한 번에 하나의 EntityEntry에만 액세스했습니다. ChangeTracker.Entries()는 현재 DbContext에서 추적하는 모든 엔터티에 대해 EntityEntry를 반환합니다. 예시:
using var context = new BlogsContext();
var blogs = context.Blogs.Include(e => e.Posts).ToList();
foreach (var entityEntry in context.ChangeTracker.Entries())
{
Console.WriteLine($"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property("Id").CurrentValue}");
}
이전 코드는 다음 출력을 생성합니다.
Found Blog entity with ID 1
Found Post entity with ID 1
Found Post entity with ID 2
블로그와 게시물 모두에 대한 항목이 반환됩니다. 대신 ChangeTracker.Entries<TEntity>() 제네릭 오버로드를 사용하여 결과를 특정 엔터티 형식으로 필터링할 수 있습니다.
foreach (var entityEntry in context.ChangeTracker.Entries<Post>())
{
Console.WriteLine(
$"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}
이 코드의 출력은 게시물만 반환됨을 보여줍니다.
Found Post entity with ID 1
Found Post entity with ID 2
또한 제네릭 오버로드를 사용하면 제네릭 EntityEntry<TEntity> 인스턴스가 반환됩니다. 이 예제에서는 흐름과 유사한 Id
속성에 액세스할 수 있습니다.
필터링에 사용되는 제네릭 형식은 매핑된 엔터티 형식일 필요가 없습니다. 매핑되지 않은 기본 형식 또는 인터페이스를 대신 사용할 수 있습니다. 예를 들어 모델의 모든 엔터티 형식이 키 속성을 정의하는 인터페이스를 구현하는 경우:
public interface IEntityWithKey
{
int Id { get; set; }
}
그런 다음 이 인터페이스를 사용하여 강력한 형식의 방식으로 추적된 엔터티의 키를 사용할 수 있습니다. 예시:
foreach (var entityEntry in context.ChangeTracker.Entries<IEntityWithKey>())
{
Console.WriteLine(
$"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}
DbSet.Local을 사용하여 추적된 엔터티 쿼리
EF Core 쿼리는 항상 데이터베이스에서 실행되며 데이터베이스에 저장된 엔터티만 반환합니다. DbSet<TEntity>.Local은 추적된 로컬 엔터티에 대해 DbContext를 쿼리하는 메커니즘을 제공합니다.
DbSet.Local
은 추적된 엔터티를 쿼리하는 데 사용되므로 DbContext에 엔터티를 로드한 다음 로드된 엔터티로 작업하는 것이 일반적입니다. 이는 데이터 바인딩에 특히 해당하지만 다른 상황에서도 유용할 수 있습니다. 예를 들어 다음 코드에서 데이터베이스는 먼저 모든 블로그 및 게시물에 대해 쿼리됩니다. Load 확장 메서드는 애플리케이션에 직접 반환되지 않고 컨텍스트에서 추적된 결과와 함께 이 쿼리를 실행하는 데 사용됩니다. (ToList
또는 이와 유사한 사용은 동일한 효과가 있지만 반환된 목록을 만드는 오버헤드는 여기에 필요하지 않습니다.) 그런 다음, DbSet.Local
을 사용하여 로컬로 추적된 엔터티에 액세스합니다.
using var context = new BlogsContext();
context.Blogs.Include(e => e.Posts).Load();
foreach (var blog in context.Blogs.Local)
{
Console.WriteLine($"Blog: {blog.Name}");
}
foreach (var post in context.Posts.Local)
{
Console.WriteLine($"Post: {post.Title}");
}
ChangeTracker.Entries()와는 달리 DbSet.Local
은 엔터티 인스턴스를 직접 반환합니다.좋아요 취소 물론 EntityEntry는 DbContext.Entry를 호출하여 반환된 엔터티에 대해 항상 가져올 수 있습니다.
로컬 뷰입니다.
DbSet<TEntity>.Local은 해당 엔터티의 현재 EntityState를 반영하는 로컬로 추적된 엔터티의 보기를 반환합니다. 특히 이는 다음을 의미합니다.
Added
엔터티가 포함됩니다.Added
엔터티가 아직 데이터베이스에 존재하지 않으므로 데이터베이스 쿼리에서 반환되지 않으므로 일반적인 EF Core 쿼리의 경우는 그렇지 않습니다.Deleted
엔터티는 제외됩니다.Deleted
엔터티가 여전히 데이터베이스에 존재하며 데이터베이스 쿼리에서 반환되므로 일반적인 EF Core 쿼리에서는 그렇지 않습니다.
이 모든 것은 DbSet.Local
은 Added
엔터티가 포함되고 Deleted
엔터티가 제외된 엔터티 그래프의 현재 개념 상태를 반영하는 데이터에 대한 보기라는 것을 의미합니다. 이는 SaveChanges가 호출된 후 예상되는 데이터베이스 상태와 일치합니다.
이는 애플리케이션에서 변경한 내용을 기반으로 데이터를 이해할 때 사용자에게 표시하기 때문에 일반적으로 데이터 바인딩에 적합한 보기입니다.
다음 코드에서는 하나의 게시물을 Deleted
로 표시한 다음 새 게시물을 추가하여 Added
로 표시하는 방법을 보여 줍니다.
using var context = new BlogsContext();
var posts = context.Posts.Include(e => e.Blog).ToList();
Console.WriteLine("Local view after loading posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
context.Remove(posts[1]);
context.Add(
new Post
{
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many...",
Blog = posts[0].Blog
});
Console.WriteLine("Local view after adding and deleting posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
이 코드의 출력은 다음과 같습니다.
Local view after loading posts:
Post: Announcing the Release of EF Core 5.0
Post: Announcing F# 5
Post: Announcing .NET 5.0
Local view after adding and deleting posts:
Post: What’s next for System.Text.Json?
Post: Announcing the Release of EF Core 5.0
Post: Announcing .NET 5.0
삭제된 게시물이 로컬 보기에서 제거되고 추가된 게시물이 포함됩니다.
로컬을 사용하여 엔터티 추가 및 제거
DbSet<TEntity>.Local가 LocalView<TEntity> 인스턴스를 반환합니다. 이는 엔터티가 컬렉션에서 추가되고 제거될 때 알림을 생성하고 응답하는 ICollection<T>의 구현입니다. (이 개념은 ObservableCollection<T>과 동일하지만 독립 컬렉션이 아닌 기존 EF Core 변경 내용 추적 항목에 대한 프로젝션으로 구현됩니다.)
로컬 보기의 알림은 DbContext 변경 내용 추적에 연결되어 로컬 보기가 DbContext와 동기화된 상태로 유지됩니다. 특별한 사항
- 새 엔터티를
DbSet.Local
추가하면 DbContext(일반적으로 상태)에서Added
추적됩니다. (엔터티에 이미 생성된 키 값이 있는 경우 대신Unchanged
로 추적됩니다.) DbSet.Local
에서 엔터티를 제거하면 엔터티가Deleted
로 표시됩니다.- DbContext에서 추적되는 엔터티가
DbSet.Local
컬렉션에 자동으로 표시됩니다. 예를 들어 더 많은 엔터티를 가져오기 위해 쿼리를 실행하면 로컬 보기가 자동으로 업데이트됩니다. Deleted
로 표시된 엔터티는 로컬 컬렉션에서 자동으로 제거됩니다.
즉, 로컬 뷰를 사용하여 단순히 컬렉션에서 추가 및 제거하여 추적된 엔터티를 조작할 수 있습니다. 예를 들어 이전 예제 코드를 수정하여 로컬 컬렉션에서 게시물을 추가하고 제거해 보겠습니다.
using var context = new BlogsContext();
var posts = context.Posts.Include(e => e.Blog).ToList();
Console.WriteLine("Local view after loading posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
context.Posts.Local.Remove(posts[1]);
context.Posts.Local.Add(
new Post
{
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many...",
Blog = posts[0].Blog
});
Console.WriteLine("Local view after adding and deleting posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
로컬 뷰에 대한 변경 내용이 DbContext와 동기화되므로 출력은 이전 예제와 변경되지 않은 상태로 유지됩니다.
Windows Forms 또는 WPF 데이터 바인딩에 로컬 뷰 사용
DbSet<TEntity>.Local은 EF Core 엔터티에 대한 데이터 바인딩의 기초를 형성합니다. 그러나 Windows Forms 및 WPF는 예상되는 특정 유형의 알림 컬렉션과 함께 사용할 때 가장 적합합니다. 로컬 뷰는 다음과 같은 특정 컬렉션 형식을 만드는 것을 지원합니다.
- LocalView<TEntity>.ToObservableCollection()은 WPF 데이터 바인딩에 대해 ObservableCollection<T>을 반환합니다.
- LocalView<TEntity>.ToBindingList()는 Windows Forms 데이터 바인딩에 대해 BindingList<T>를 반환합니다.
예시:
ObservableCollection<Post> observableCollection = context.Posts.Local.ToObservableCollection();
BindingList<Post> bindingList = context.Posts.Local.ToBindingList();
EF Core를 사용한 WPF 데이터 바인딩에 대한 자세한 내용은 WPF 시작을, EF Core를 사용한 Windows Forms 데이터 바인딩에 대한 자세한 내용은 Windows Forms 시작을 참조하세요.
팁
지정된 DbSet 인스턴스에 대한 로컬 보기는 처음 액세스한 다음 캐시될 때 지연적으로 만들어집니다. LocalView 만들기 자체는 빠르며 상당한 메모리를 사용하지 않습니다. 그러나 많은 수의 엔터티에 대해 느릴 수 있는 DetectChanges를 호출합니다. ToObservableCollection
및 ToBindingList
에서 만든 컬렉션도 지연적으로 생성된 다음 캐시됩니다. 이러한 두 메서드는 모두 새 컬렉션을 만들며, 수천 개의 엔터티가 관련될 때 속도가 느리고 메모리를 많이 사용할 수 있습니다.
.NET