Локальные данные

Выполнение запроса LINQ непосредственно в DbSet всегда отправляет запрос в базу данных, но вы можете получить доступ к данным, которые в настоящее время находятся в памяти с помощью свойства DbSet.Local. Вы также можете получить доступ к дополнительным сведениям EF для отслеживания сущностей с помощью методов DbContext.Entry и DbContext.ChangeTracker.Entries. Методы, представленные в этом разделе, также применимы к моделям, созданным с помощью Code First и конструктора EF.

Использование local для просмотра локальных данных

Локальное свойство DbSet предоставляет простой доступ к сущностям набора, которые в настоящее время отслеживаются контекстом и не помечены как Удаленные. Доступ к локальному свойству никогда не приводит к отправке запроса в базу данных. Это означает, что он обычно используется после выполнения запроса. Метод расширения 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);
    }
}

Если у нас есть два блога в базе данных - "ADO.NET блог" с BlogId 1 и "Блог Visual Studio" с BlogId 2 - мы могли ожидать следующие выходные данные:

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

В этом примере показаны три пункта:

  • Новый блог "Мой новый блог" включен в локальную коллекцию, хотя он еще не был сохранен в базе данных. В этом блоге имеется первичный ключ нуля, так как база данных еще не создала реальный ключ для сущности.
  • Блог ADO.NET не включен в локальную коллекцию, несмотря на то, что он по-прежнему отслеживается контекстом. Это связано с тем, что мы удалили его из DbSet, тем самым помечая его как удаленный.
  • Если DbSet используется для выполнения запроса блога, помеченного для удаления (ADO.NET блога), включается в результаты и новый блог (мой новый блог), который еще не был сохранен в базе данных, не включен в результаты. Это связано с тем, что DbSet выполняет запрос к базе данных, а результаты, возвращаемые, всегда отражают то, что находится в базе данных.

Использование local для добавления и удаления сущностей из контекста

Локальное свойство в DbSet возвращает observableCollection с событиями, подключенными к ним, чтобы он оставался синхронизированным с содержимым контекста. Это означает, что сущности можно добавлять или удалять из локальной коллекции или DbSet. Это также означает, что запросы, которые переносят новые сущности в контекст, приведет к обновлению локальной коллекции с этими сущностями. Например:

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

В этом примере показаны три пункта:

  • Новая запись "Новые возможности EF", добавленная в локальную коллекцию, отслеживается контекстом в добавленном состоянии. Поэтому он будет вставлен в базу данных при вызове SaveChanges.
  • Запись, удаленная из локальной коллекции (РУКОВОДСТВО для начинающих EF), теперь помечена как удаленная в контексте. Поэтому он будет удален из базы данных при вызове SaveChanges.
  • В локальную коллекцию автоматически добавляется дополнительная запись (ASP.NET руководство для начинающих), загруженная в контекст со вторым запросом.

Последнее, что следует отметить о local, заключается в том, что это производительность ObservableCollection не отлично подходит для большого количества сущностей. Таким образом, если вы работаете с тысячами сущностей в контексте, возможно, не рекомендуется использовать local.

Использование локальной привязки данных WPF

Локальное свойство dbSet можно использовать непосредственно для привязки данных в приложении WPF, так как это экземпляр ObservableCollection. Как описано в предыдущих разделах, это означает, что он автоматически будет синхронизироваться с содержимым контекста, а содержимое контекста будет автоматически оставаться в синхронизации с ним. Обратите внимание, что необходимо предварительно заполнить локальную коллекцию данными для привязки к локальной коллекции, так как local никогда не вызывает запрос базы данных.

Это не подходящее место для полного примера привязки данных WPF, но ключевые элементы:

  • Настройка источника привязки
  • Привязать его к свойству Local набора
  • Заполните локальный параметр с помощью запроса к базе данных.

Привязка WPF к свойствам навигации

Если вы выполняете привязку данных master/detail, может потребоваться привязать представление сведений к свойству навигации одного из ваших сущностей. Простой способ сделать эту работу — использовать 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

В большинстве случаев сущности, удаленные из свойства навигации, не будут автоматически помечены как удаленные в контексте. Например, если удалить объект Post из коллекции Blog.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();
}

Приведенный выше код использует локальную коллекцию для поиска всех записей и помечает все записи, которые не имеют ссылки на блог как удаленные. Вызов ToList требуется, так как в противном случае коллекция будет изменена вызовом Remove во время перечисления. В большинстве других ситуаций вы можете запрашивать непосредственно к локальному свойству без использования ToList сначала.

Использование привязки данных Local и ToBindingList для Windows Forms

Windows Forms не поддерживает полную привязку данных точности с помощью ObservableCollection напрямую. Однако вы по-прежнему можете использовать свойство DbSet Local для привязки данных, чтобы получить все преимущества, описанные в предыдущих разделах. Это достигается с помощью метода расширения ToBindingList, который создает реализацию IBindingList , поддерживаемую local ObservableCollection.

Это не подходящее место для полного примера привязки данных Windows Forms, но ключевые элементы:

  • Настройка источника привязки объекта
  • Привязать его к свойству Local набора с помощью Local.ToBindingList()
  • Заполнение локального объекта с помощью запроса к базе данных

Получение подробных сведений об отслеживаемых сущностях

Во многих примерах в этой серии используется метод 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"
Блог с blogId = 2 и имя = "Блог Visual Studio"
Блог с BlogId = 3 и имя = "платформа .NET Framework блог"
Автор с authorId = 1 и имя = "Джо Блоггс"
Читатель с ReaderId = 1 и имя = "Джон 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

В этих примерах показано несколько моментов:

  • Методы записей возвращают записи для сущностей во всех состояниях, включая Deleted. Сравните это с локальным, которое исключает удаленные сущности.
  • Записи для всех типов сущностей возвращаются при использовании метода не универсальных записей. Если метод универсальных записей используется, возвращаются только для сущностей, которые являются экземплярами универсального типа. Это было использовано выше для получения записей для всех блогов. Он также использовался для получения записей для всех сущностей, реализующих IPerson. В этом примере показано, что универсальный тип не должен быть фактическим типом сущности.
  • LINQ to Objects можно использовать для фильтрации возвращаемых результатов. Это было использовано выше для поиска сущностей любого типа, если они изменены.

Обратите внимание, что экземпляры DbEntityEntry всегда содержат сущность, не являющуюся null. Записи связи и заглушки не представлены как экземпляры DbEntityEntry, поэтому не требуется фильтровать их.