로컬 데이터

DbSet에 대해 LINQ 쿼리를 직접 실행하면 항상 데이터베이스에 쿼리가 전송되지만 DbSet.Local 속성을 사용하여 현재 메모리에 있는 데이터에 액세스할 수 있습니다. DbContext.Entry 및 DbContext.ChangeTracker.Entries 메서드를 사용하여 EF가 엔터티에 대해 추적하는 추가 정보에 액세스할 수도 있습니다. 이 토픽에서 설명하는 방법은 Code First 및 EF 디자이너를 사용하여 만든 모델에 동일하게 적용됩니다.

로컬을 사용하여 로컬 데이터 보기

DbSet의 Local 속성은 현재 컨텍스트로 추적되고 Deleted로 표시되지 않은 집합의 엔터티에 대한 간단한 액세스를 허용합니다. Local 속성에 액세스하면 쿼리가 데이터베이스로 전송되지 않습니다. 즉, 일반적으로 쿼리가 이미 수행된 후에 사용됩니다. Load 확장 메서드를 통해 쿼리를 실행하여 컨텍스트로 결과를 추적할 수 있습니다. 예시:

using (var context = new BloggingContext())
{
    // Load all blogs from the database into the context
    context.Blogs.Load();

    // Add a new blog to the context
    context.Blogs.Add(new Blog { Name = "My New Blog" });

    // Mark one of the existing blogs as Deleted
    context.Blogs.Remove(context.Blogs.Find(1));

    // Loop over the blogs in the context.
    Console.WriteLine("In Local: ");
    foreach (var blog in context.Blogs.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            blog.BlogId,  
            blog.Name,
            context.Entry(blog).State);
    }

    // Perform a query against the database.
    Console.WriteLine("\nIn DbSet query: ");
    foreach (var blog in context.Blogs)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            blog.BlogId,  
            blog.Name,
            context.Entry(blog).State);
    }
}

데이터베이스에 BlogId가 1인 'ADO.NET Blog'와 BlogId가 2인 'The Visual Studio Blog'라는 두 개의 블로그가 있는 경우 예상되는 출력은 다음과 같습니다.

In Local:
Found 0: My New Blog with state Added
Found 2: The Visual Studio Blog with state Unchanged

In DbSet query:
Found 1: ADO.NET Blog with state Deleted
Found 2: The Visual Studio Blog with state Unchanged

이는 다음 세 가지 사항을 보여 줍니다.

  • 새 블로그인 ‘My New Blog’는 아직 데이터베이스에 저장되지 않았지만 로컬 컬렉션에 있습니다. 데이터베이스가 엔터티에 대한 실제 키를 아직 생성하지 않았으므로 이 블로그의 기본 키는 0입니다.
  • ‘ADO.NET Blog’가 계속 컨텍스트로 추적되고 있어도 로컬 컬렉션에 없습니다. DbSet에서 제거하여 삭제된 것으로 표시하였기 때문입니다.
  • DbSet을 사용하여 쿼리를 수행하는 경우 삭제로 표시된 블로그(ADO.NET Blog)가 결과에 포함되고 데이터베이스에 아직 저장되지 않은 새 블로그(My New Blog)는 결과에 포함되지 않습니다. 이는 DbSet이 데이터베이스에 대해 쿼리를 수행하고 반환된 결과가 항상 데이터베이스에 있는 내용을 반영하기 때문입니다.

Local을 사용하여 컨텍스트에서 엔터티 추가 및 제거

DbSet의 Local 속성은 컨텍스트의 콘텐츠와 동기화된 상태로 유지되도록 이벤트가 연결된 ObservableCollection을 반환합니다. 즉, Local 컬렉션 또는 DbSet에서 엔터티를 추가하거나 제거할 수 있습니다. 또한 새 엔터티를 컨텍스트로 가져오는 쿼리로 인해 Local 컬렉션이 해당 엔터티로 업데이트됩니다. 예시:

using (var context = new BloggingContext())
{
    // Load some posts from the database into the context
    context.Posts.Where(p => p.Tags.Contains("entity-framework")).Load();  

    // Get the local collection and make some changes to it
    var localPosts = context.Posts.Local;
    localPosts.Add(new Post { Name = "What's New in EF" });
    localPosts.Remove(context.Posts.Find(1));  

    // Loop over the posts in the context.
    Console.WriteLine("In Local after entity-framework query: ");
    foreach (var post in context.Posts.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            post.Id,  
            post.Title,
            context.Entry(post).State);
    }

    var post1 = context.Posts.Find(1);
    Console.WriteLine(
        "State of post 1: {0} is {1}",
        post1.Name,  
        context.Entry(post1).State);  

    // Query some more posts from the database
    context.Posts.Where(p => p.Tags.Contains("asp.net")).Load();  

    // Loop over the posts in the context again.
    Console.WriteLine("\nIn Local after asp.net query: ");
    foreach (var post in context.Posts.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            post.Id,  
            post.Title,
            context.Entry(post).State);
    }
}

'entity-framework' 및 'asp.net'으로 태그가 지정된 게시물이 몇 개 있다고 가정하면 표시될 수 있는 출력은 다음과 같습니다.

In Local after entity-framework query:
Found 3: EF Designer Basics with state Unchanged
Found 5: EF Code First Basics with state Unchanged
Found 0: What's New in EF with state Added
State of post 1: EF Beginners Guide is Deleted

In Local after asp.net query:
Found 3: EF Designer Basics with state Unchanged
Found 5: EF Code First Basics with state Unchanged
Found 0: What's New in EF with state Added
Found 4: ASP.NET Beginners Guide with state Unchanged

이는 다음 세 가지 사항을 보여 줍니다.

  • Local 컬렉션에 추가된 새 게시물인 'What's New in EF'는 Added 상태의 컨텍스트로 추적됩니다. 따라서 SaveChanges가 호출되면 데이터베이스에 삽입됩니다.
  • Local 컬렉션(EF Beginners Guide)에서 제거된 게시물은 이제 컨텍스트에서 삭제된 것으로 표시됩니다. 따라서 SaveChanges가 호출되면 데이터베이스에서 삭제됩니다.
  • 두 번째 쿼리를 사용하여 컨텍스트에 로드된 추가 게시물(ASP.NET Beginners Guide)이 Local 컬렉션에 자동으로 추가됩니다.

Local에 대해 마지막으로 유의해야 할 한 가지는 ObservableCollection 성능이기 때문에 엔터티 수가 많으면 성능에 좋지 않다는 것입니다. 따라서 컨텍스트에서 수많은 엔터티를 처리해야 할 경우 Local을 사용하는 것은 좋지 않습니다.

WPF 데이터 바인딩에 Local 사용

DbSet의 Local 속성은 ObservableCollection의 인스턴스이므로 WPF 애플리케이션의 데이터 바인딩에 직접 사용할 수 있습니다. 이전 섹션에서 설명한 대로 이는 컨텍스트의 콘텐츠와 동기화된 상태를 자동으로 유지하고 컨텍스트의 콘텐츠도 자동으로 동기화된 상태를 유지한다는 것을 의미합니다. Local이 데이터베이스 쿼리를 유발하지 않으므로 바인딩할 항목이 있도록 Local 컬렉션을 데이터로 미리 채워야 합니다.

이는 전체 WPF 데이터 바인딩 샘플에 적합한 위치는 아니지만 핵심 요소는 다음과 같습니다.

  • 바인딩 소스 설정
  • 집합의 Local 속성에 바인딩
  • 데이터베이스에 대한 쿼리를 사용하여 Local 채우기

탐색 속성에 대한 WPF 바인딩

마스터/세부 정보 데이터 바인딩을 수행하는 경우 엔터티 중 하나의 탐색 속성에 세부 정보 보기를 바인딩할 수 있습니다. 이 작업을 간편하게 수행하는 방법은 탐색 속성에 ObservableCollection을 사용하는 것입니다. 예시:

public class Blog
{
    private readonly ObservableCollection<Post> _posts =
        new ObservableCollection<Post>();

    public int BlogId { get; set; }
    public string Name { get; set; }

    public virtual ObservableCollection<Post> Posts
    {
        get { return _posts; }
    }
}

Local을 사용하여 SaveChanges에서 엔터티 정리

대부분의 경우 탐색 속성에서 제거된 엔터티는 컨텍스트에서 삭제된 것으로 자동 표시되지 않습니다. 예를 들어 Blog.Posts 컬렉션에서 Post 개체를 제거하면 SaveChanges가 호출될 때 해당 게시물이 자동으로 삭제되지 않습니다. 삭제해야 하는 경우에는 SaveChanges를 호출하기 전에 또는 재정의된 SaveChanges의 일부로 이러한 현수 엔터티를 찾아 삭제된 것으로 표시해야 할 수 있습니다. 예시:

public override int SaveChanges()
{
    foreach (var post in this.Posts.Local.ToList())
    {
        if (post.Blog == null)
        {
            this.Posts.Remove(post);
        }
    }

    return base.SaveChanges();
}

위의 코드는 Local 컬렉션을 사용하여 모든 게시물을 찾고 블로그 참조가 없는 모든 게시물을 삭제된 것으로 표시합니다. 이 경우 ToList 호출이 필요합니다. 그렇지 않으면 컬렉션이 열거되는 동안 Remove 호출로 수정됩니다. 대부분의 다른 상황에서는 ToList를 먼저 사용하지 않고 Local 속성에 대해 직접 쿼리할 수 있습니다.

Windows Forms 데이터 바인딩에 Local 및 ToBindingList 사용

Windows Forms는 ObservableCollection을 직접 사용하여 최고 충실도의 데이터 바인딩을 지원하지 않습니다. 그러나 데이터 바인딩에 DbSet Local 속성을 계속 사용하여 이전 섹션에서 설명한 모든 이점을 활용할 수 있습니다. 이는 Local ObservableCollection에서 지원되는 IBindingList 구현을 만드는 ToBindingList 확장 메서드를 통해 수행됩니다.

이는 전체 Windows Forms 데이터 바인딩 샘플에 적합한 위치는 아니지만 핵심 요소는 다음과 같습니다.

  • 개체 바인딩 소스 설정
  • Local.ToBindingList()를 사용하여 집합의 Local 속성에 바인딩
  • 데이터베이스에 대한 쿼리를 사용하여 Local 채우기

추적된 엔터티에 대한 자세한 정보 가져오기

이 시리즈의 많은 예제에서는 Entry 메서드를 사용하여 엔터티에 대한 DbEntityEntry 인스턴스를 반환합니다. 그런 다음 이 항목 개체는 현재 상태와 같은 엔터티에 대한 정보를 수집하고 관련 엔터티를 명시적으로 로드하는 등 엔터티에 대한 작업을 수행하기 위한 시작점 역할을 합니다.

Entries 메서드는 컨텍스트로 추적되는 많은 엔터티 또는 모든 엔터티에 대해 DbEntityEntry 개체를 반환합니다. 이렇게 하면 단일 항목이 아닌 많은 엔터티에서 정보를 수집하거나 작업을 수행할 수 있습니다. 예시:

using (var context = new BloggingContext())
{
    // Load some entities into the context
    context.Blogs.Load();
    context.Authors.Load();
    context.Readers.Load();

    // Make some changes
    context.Blogs.Find(1).Title = "The New ADO.NET Blog";
    context.Blogs.Remove(context.Blogs.Find(2));
    context.Authors.Add(new Author { Name = "Jane Doe" });
    context.Readers.Find(1).Username = "johndoe1987";

    // Look at the state of all entities in the context
    Console.WriteLine("All tracked entities: ");
    foreach (var entry in context.ChangeTracker.Entries())
    {
        Console.WriteLine(
            "Found entity of type {0} with state {1}",
            ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
            entry.State);
    }

    // Find modified entities of any type
    Console.WriteLine("\nAll modified entities: ");
    foreach (var entry in context.ChangeTracker.Entries()
                              .Where(e => e.State == EntityState.Modified))
    {
        Console.WriteLine(
            "Found entity of type {0} with state {1}",
            ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
            entry.State);
    }

    // Get some information about just the tracked blogs
    Console.WriteLine("\nTracked blogs: ");
    foreach (var entry in context.ChangeTracker.Entries<Blog>())
    {
        Console.WriteLine(
            "Found Blog {0}: {1} with original Name {2}",
            entry.Entity.BlogId,  
            entry.Entity.Name,
            entry.Property(p => p.Name).OriginalValue);
    }

    // Find all people (author or reader)
    Console.WriteLine("\nPeople: ");
    foreach (var entry in context.ChangeTracker.Entries<IPerson>())
    {
        Console.WriteLine("Found Person {0}", entry.Entity.Name);
    }
}

예제에 보이는 대로 Author 및 Reader 클래스가 도입되어 있습니다. 두 클래스는 모두 IPerson 인터페이스를 구현합니다.

public class Author : IPerson
{
    public int AuthorId { get; set; }
    public string Name { get; set; }
    public string Biography { get; set; }
}

public class Reader : IPerson
{
    public int ReaderId { get; set; }
    public string Name { get; set; }
    public string Username { get; set; }
}

public interface IPerson
{
    string Name { get; }
}

데이터베이스에 다음 데이터가 있다고 가정해 보겠습니다.

BlogId가 1이고 이름이 'ADO.NET Blog'인 블로그
BlogId가 2이고 이름이 'The Visual Studio Blog'인 블로그
BlogId가 3이고 이름이 '.NET Framework Blog'인 블로그
AuthorId가 1이고 이름이 'Joe Bloggs'인 작성자
ReaderId가 1이고 이름이 'John Doe'인 읽기 권한자

코드를 실행한 후 출력은 다음과 같습니다.

All tracked entities:
Found entity of type Blog with state Modified
Found entity of type Blog with state Deleted
Found entity of type Blog with state Unchanged
Found entity of type Author with state Unchanged
Found entity of type Author with state Added
Found entity of type Reader with state Modified

All modified entities:
Found entity of type Blog with state Modified
Found entity of type Reader with state Modified

Tracked blogs:
Found Blog 1: The New ADO.NET Blog with original Name ADO.NET Blog
Found Blog 2: The Visual Studio Blog with original Name The Visual Studio Blog
Found Blog 3: .NET Framework Blog with original Name .NET Framework Blog

People:
Found Person John Doe
Found Person Joe Bloggs
Found Person Jane Doe

이 예제는 다음 몇 가지 사항을 보여 줍니다.

  • Entries 메서드는 Deleted를 비롯한 모든 상태의 엔터티의 항목을 반환합니다. Deleted 엔터티를 제외하는 Local과 비교해 보세요.
  • 제네릭이 아닌 Entries 메서드를 사용하면 모든 엔터티 형식에 대한 항목이 반환됩니다. 제네릭 항목 메서드를 사용하면 제네릭 형식의 인스턴스인 엔터티의 항목만 반환됩니다. 위에서는 모든 블로그의 항목을 가져오는 데 사용되었습니다. IPerson을 구현하는 모든 엔터티의 항목을 가져오는 데도 사용되었습니다. 이는 제네릭 형식이 실제 엔터티 형식일 필요는 없음을 보여 줍니다.
  • LINQ to Objects를 사용하여 반환된 결과를 필터링할 수 있습니다. 위에서는 수정되는 모든 형식의 엔터티를 찾기 위해 사용되었습니다.

DbEntityEntry 인스턴스에는 항상 null이 아닌 엔터티가 포함됩니다. 관계 항목 및 스텁 항목은 DbEntityEntry 인스턴스로 표시되지 않으므로 필터링할 필요가 없습니다.