Введение в entity framework

Введение в LINQ to Entities

Последнее обновление: 31.10.2015

Ранее мы использовали ряд операций для получения данных из БД. В основе подобных операций лежит технология LINQ (Language Integrated Query), или точнее
LINQ to Entities. LINQ to Entities предлагает простой и интуитивно понятный подход для получения данных с помощью выражений,
которые по форме близки выражениям языка SQL.

Хотя при работе с базой данных мы оперируем запросами LINQ, но база данных понимает только запросы на языке SQL. Поэтому между LINQ to Entities и
базой данных есть проводник, который позволяет им взаимодействовать. Этим проводником является провайдер EntityClient. Он создает интерфейс для взаимодействия с провайдером ADO.NET для
SQL Serverа.

Для начала взаимодействия с базой данных создается объект EntityConnection. Через объект EntityCommand
он отправляет запросы, а с помощью объекта EntityDataReader считывает извлеченные из БД данные. Однако разработчику не надо напрямую взаимодействовать с
этими объектами, фреймворк все сделает за него. Задача же разработчика сводится в основном к написанию запросов к базе данных с помощью LINQ.

Прежде чем приступить к обзору основных запросов в LINQ to Entities, для работы с материалом этой главы создадим новые модели по связи один-ко-многим:

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }

    public ICollection<Phone> Phones { get; set; }
    public Company()
    {
        Phones = new List<Phone>();
    }
}

public class Phone
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }

    public int CompanyId { get; set; }
    public Company Company { get; set; }
}

У нас здесь модель телефона и модель компании-производителя. Теперь создадим контекст данных и инициализатор базы данных начальными данными:

class PhoneContext : DbContext
{
    static PhoneContext()
    {
        Database.SetInitializer(new MyContextInitializer());
    }
    public PhoneContext() :base("DefaultConnection")
    {}

    public DbSet<Company> Companies { get; set; }
    public DbSet<Phone> Phones { get; set; }
}

class MyContextInitializer : DropCreateDatabaseAlways<PhoneContext>
{
    protected override void Seed(PhoneContext db)
    {
        Company c1 = new Company { Name = "Samsung" };
        Company c2 = new Company { Name = "Apple" };
        db.Companies.Add(c1);
        db.Companies.Add(c2);

        db.SaveChanges();

        Phone p1 = new Phone {Name="Samsung Galaxy S5", Price=20000, Company = c1};
        Phone p2 = new Phone {Name="Samsung Galaxy S4", Price=15000, Company = c1};
        Phone p3 = new Phone {Name="iPhone5", Price=28000, Company = c2};
        Phone p4 = new Phone {Name="iPhone 4S", Price=23000, Company = c2};

        db.Phones.AddRange(new List<Phone>(){p1, p2, p3, p4});
        db.SaveChanges();
    }
}

Чтобы база данных уже содержала некоторые данные, в инициализаторе бд создается несколько объектов. Чтобы задействовать инициализатор, он вызывается в статическом конструкторе
контекста данных:

Для создания запросов в Linq to Entities, так же, как и в Linq to Objects, мы можем применять операторы LINQ и методы расширения LINQ.

Например, используем некоторые операторы LINQ:

using(PhoneContext db = new PhoneContext())
{
    var phones = from p in db.Phones
                where p.CompanyId == 1
                select p;
}

И тот же запрос с помощью методов расширений LINQ:

using(PhoneContext db = new PhoneContext())
{
	var phones = db.Phones.Where(p=> p.CompanyId == 1);
}

Оба запроса в итоге транслируются в одной выражение sql:

SELECT . AS , 
	   . AS , 
	   . AS , 
	   . AS     
FROM . AS     
WHERE 1 = .}

Важно понимать различие между Linq to Entities и Linq to Objects:

var phones = db.Phones.Where(p=> p.CompanyId == 1).ToList().Where(p=> p.Id<10);

Здесь используются два метода Where, но их реализация будет различной. В первом случае, транслируется
в выражение SQL, которое было рассмотрено выше. Далее метод по результатам запроса создает список в памяти компьютера. После этого мы
уже имеем дело со списком в памяти, а не с базой данных. И далее вызов будет обращаться к списку в памяти и будет представлять
Linq to Object.

А теперь рассмотрим некоторые приемы применения LINQ к запросам из базы данных.

НазадВперед

Подход Code-First

Вы можете использовать этот класс модели в своем приложении без создания базы данных. Добавив дополнительный код, вы могли бы хранить объекты этого класса, например, на диске в формате XML или в памяти рабочего процесса программы. Однако когда ваше приложение расширяется и становится крупным, эти данные необходимо будет хранить в базе данных

Эта та точка, в которой и начинает работать подход Code-First – вы можете использовать существующий код для создания базы данных не беспокоясь о деталях реализации базы данных (этим займется Entity Framework), а вы можете сфокусировать свое внимание на коде

Важно отметить, что класс, представляющий модель данных, должен иметь поле Id, которое будет использоваться в таблице базы данных в качестве первичного ключа. Entity Framework автоматически находит такое поле с помощью механизма рефлексии (в его имени должна содержаться строка “Id”, поэтому поле CustomerId в примере выше будет автоматически использоваться в качестве первичного ключа.) Это ограничение можно обойти, использовав свойство с произвольным именем и помеченное специальными атрибутами метаданных C#, которые используются в Entity Framework

Здесь мы их описывать не будем, я лишь заострил свое внимание на этом моменте, потому что он показывает, что любые настройки первичных и внешних ключей, ограничения между таблицами и т.д. при подходе Code-First указываются в модели (т.е

в управляемом коде C#), а потом проецируются на базу данных.

Подход Code-First появился позже подходов Model-First и Database-First и, как вы уже поняли, больше всего подходит для разработчиков, которые хотят писать код, а не работать с дизайнером модели EDM или средствами работы с базами данных (SQL Server Management Studio и T-SQL). Вы можете создать модель для вашего приложения, используя объекты CLR (Common Language Runtime) и специальные объекты POCO (Plain Old CLR Object).

При проектировании приложений с подходом Code-First, вы сначала создаете классы модели данных не обращая никакого внимания на Entity Framework. После того, как вам понадобилось работать с базой данных, вы используете различные инструменты, которые проецируют структуру базы данных из созданной модели классов. После этого вы можете вернуться к этой модели в коде и, например, изменить ее. Эти изменения затем можно будет отразить в базе данных используя все те же инструменты.

Важным нововведением версии Entity Framework 5 в плане подхода Code-First, является то, что созданная модель классов теперь сразу является сущностной моделью данных EDM (Entity Data Model), поэтому отпала необходимость использовать файл EDMX. В более ранних версиях разработчику, использующему подход Code-First, приходилось добавлять отношения между моделью классов и файлом EDMX, т.е. отображать любые изменения модели сразу в двух местах. Очевидно, что этот подход приводил к появлению кучи ошибок, если разработчик забывал синхронизировать эти изменения в обоих файлах.

Чтобы указать среде Visual Studio, что модель классов является моделью EDM, нужно во-первых установить сборки Entity Framework в проект, а во-вторых добавить класс контекста базы данных, унаследованный от класса DbContext, находящегося в пространстве имен System.Data.Entity, как показано в примере ниже (установку Entity Framework и настройку класса контекста мы более подробно будем рассматривать позже):

Вам не нужно беспокоится о способах взаимодействия с базой данных, Entity Framework определяет ряд вспомогательных методов. Вам не нужно знать деталей подключения и даже имени базы данных. Ниже показан пример вставки данных в таблицу с использованием тестового класса Customer, показанного выше:

Этот код является немного упрощенным, но все же показывает суть работы с Entity Framework. Конечно, вы можете глубже контролировать каждую операцию с базой данных. Этот пример использует максимальную автоматизацию и дает приемлемые результаты, по крайней мере в небольшом приложении. В последующих статьях мы рассмотрим более подробно настройки подхода Code-First, которые можно использовать для удовлетворения конкретных потребностей.

Подключение к существующей базе данных

Последнее обновление: 14.11.2020

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

Для подключения к существующей базе данных создадим новый проект. Как и в прошлой теме это будет проект по типу Console App (.NET Core),
который назовем FirstApp.

Для работы с существующей БД MS SQL Server нам надо добавить два пакета:

  • Microsoft.EntityFrameworkCore.SqlServer (представляет функциональность Entity Framework для работы с MS SQL Server)

  • Microsoft.EntityFrameworkCore.Tools (необходим для создания классов по базе данных, то есть reverse engineering)

К примеру возьмем базу данных, созданную в прошлой теме. Эта база данных называется helloappdb.mdf и имеет одну таблицу Users с тремя столбцами Id, Name и
Age.

Чтобы подключаться к базе данных, нам надо будет добавить в проект классы моделей, которые соответствуют определениям таблиц, и класс контекста данных,
который соответствует БД. То есть в данном случае мы могли бы вручную добавить в проект классы моделей и класс контекста данных, как в прошлой теме.

Однако добавление классов вручную имеет свои недостатки. Так, если база данных имеет не одну, а множество таблиц, связанных различными ключами, то
у нас может возникнуть проблема, как все эти отношения отобразить между класса на C#. Ну и кроме того, это просто долго и может занять некоторое время.

Для решения этих проблем в Entity Framework Core предусмотрена функция Reverse Engineering, которая позволяет автоматически создать все
необходимые классы по базе данных. Чтобы воспользоваться этой функцией перейдем в Visual Studio к окну Package Manager Console.
Его открыть можно, перейдя в меню Tools –> NuGet Package Manager –> Package Manager Console

Далее в Package Manager Console выполним следующую команду:

Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer

Здесь в качестве параметра команде Scaffold-DbContext передается строка подключения с указанием сервера и названием базы данных.

После выполнения этой команды в проект будет добавлен класс Users:

public partial class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

И также будет добавлен класс контекста данных, который будет называться по имени базы данных плюс суффикс «Context»:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

#nullable disable

namespace FirstApp
{
    public partial class helloappdbContext : DbContext
    {
        public helloappdbContext()
        {
        }

        public helloappdbContext(DbContextOptions options)
            : base(options)
        {
        }

        public virtual DbSet<User> Users { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
				optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;");
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            OnModelCreatingPartial(modelBuilder);
        }

        partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
    }
}

И после генерации данных классов мы сможем работать с базой данных. Для этого изменим код класса Program:

using System;
using System.Linq;

namespace FirstApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (helloappdbContext db = new helloappdbContext())
            {
                // получаем объекты из бд и выводим на консоль
                var users = db.Users.ToList();
                Console.WriteLine("Список объектов:");
                foreach (User u in users)
                {
                    Console.WriteLine($"{u.Id}.{u.Name} - {u.Age}");
                }
            }
			Console.ReadKey();
        }
    }
}

НазадВперед

Аннотации

Последнее обновление: 26.09.2016

Аннотации представляют настройку сопоставления моделей и таблиц с помощью атрибутов. Большинство классов аннотаций располагаются в пространстве
, которое нам надо подключить в файл c# перед использованием аннотаций.

Настройка ключа

Для установки свойства в качестве первичного ключа применяется атрибут :

public class Phone
{
    
    public int Ident { get; set; }
    public string Name { get; set; }
}

Теперь свойство Ident будет рассматриваться в качестве первичного ключа.

Чтобы установить ключа в качестве идентификатора, можно использовать атрибут :


public int Ident { get; set; }

Если свойство, помеченное атрибутом , представляет тип , то к нему атрибут
будет применяться автоматически, и его можно в принципе не указывать.

Атрибут Required

Атрибут Required указывает, что данное свойство обязательно для установки, то есть будет иметь определение NOT NULL в БД:

public class Phone
{
    
    public int Ident { get; set; }
	
    public string Name { get; set; }
}

Если мы не установим свойство Name у объекта Phone и попытаемся добавить этот объект в бд, то получим ошибку. А столбец Name будет определен как NOT NULL.

MaxLength и MinLength

Атрибуты MaxLength и MinLength устанавливают максимальное и минимальное количество символов в строке-свойстве:

public class Phone
{
    
    public int Ident { get; set; }
    
    public string Name { get; set; }
}

Атрибут NotMapped

По умолчанию все публичные свойства сопоставляются с определенными столбцами в таблицами. Но такое поведение не всегда необходимо. Иногда требуется, наоборот,
исключить определенное свойство, чтобы для него не создавался столбец в таблице. И для этих целей есть атрибут NotMapped:

public class Phone
{
    
    public int Ident { get; set; }
	
    public string Name { get; set; }
	
    public int Discount { get; set; }
}

Чтобы задействовать атрибут NotMapped, надо подключить пространство имен

Сопоставление с таблицей и столбцами

Entity Framework при создании и сопоставлении таблиц и столбцов использует имена моделей и их свойств. Но мы можем переопределить это поведение с
помощью атрибутов Table и Column:


public class Phone
{
    public int Id { get; set; }
    
    public string Name { get; set; }
}

Теперь сущность Phone будет сопоставляться с таблицей Mobiles, а свойство Name со столбцом ModelName:

Установка внешнего ключа

Чтобы установить внешний ключ для связи с другой сущностью, используется атрибут ForeignKey:

public class Phone
{
    public int Id { get; set; }
    public string Name { get; set; }

    public int? CompId { get; set; }
    
    public Company Company { get; set; }
}

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }
}

В данном случае внешним ключом для связи с моделью Company будет служить свойство CompId.

Настройка индекса

Для установки индекса для столбца к соответствующему свойству модели применяется атрибут

public class Phone
{
	
    public int Id { get; set; }
    public string Name { get; set; }
}

ConcurrencyCheck

Атрибут ConcurrencyCheck позволяет решить проблему параллелизма, когда с одной и той же записью в таблице могут работать одновременно
несколько пользователей. Например:

public class Phone
{
    public int Id { get; set; }
	
    public string Name { get; set; }
}

Теоретически возможна ситуация, когда два пользователя пытаются изменить значение, например:

using(FluentContext db = new FluentContext())
{
    Phone phone = db.Phones.Find(1);
    phone.Name = "Nokia N9";
    db.SaveChanges();
}

В обычном режиме Entity Framework при обновлении смотрит на Id и если Id записи в таблице совпадает с Id в передаваемой модели phone, то строка в таблице обновляется.
При использовании атрибута ConcurrencyCheck EF смотрит не только на Id, но и на исходное значение свойства Name. И если оно совпадает с тем, что имеется в таблице,
то запись обновляется. Если же не совпадает (то есть кто-то уже успел обновить), то EF генерирует исключение DbUpdateConcurrencyException.

НазадВперед

Обновление одного объекта

Допустим в таблице Customers существуют покупатели с фамилиями “Иванов”. Мы хотим поменять фамилии этих покупателей на “Сидоров”. Сделать это можно с помощью следующего кода:

Этот пример использует запрос LINQ для загрузки первого попавшегося покупателя с фамилией Иванов. Затем мы изменяем фамилию пользователя и вызываем метод SaveChanges(), при котором Entity Framework определяет, что объект сущности изменился и его нужно обновить используя следующий SQL-запрос:

Как описывалось в предыдущей статье, для указания на изменение данных можно использовать модель состояний Entity Framework. Состояние сущностного объекта указывается через свойство State класса DbEntityEntry, который в свою очередь доступен через свойство DbSet.Entry. Для указания на то, что объект изменился используется значение EntityState.Modified. Ниже показан пример, как мы можем обновить фамилии всех Ивановых в таблице, а не только первого попавшегося:

Этот код сгенерирует по одной инструкции UPDATE для каждого найденного покупателя с фамилией Иванов. Стоит отметить, что в показанных выше запросах мы загружали все данные пользователя, а во втором примере при использовании состояний, мы обновляли все данные, хотя достаточно лишь обновить только фамилию. (При автоматическом обновлении свойств модели, как в первом примере, Entity Framework будет обновлять только столбец LastName, как показано в сгенерированном SQL-коде). Класс DbEntityEntry содержит метод Property(), в котором можно явно указать свойство модели, которое нужно обновлять. Ниже показан более оптимизированный запрос для смены фамилий всех пользователей, в котором мы загружаем только их идентификаторы и обновляем только фамилии:

Метод Property() возвращает объект DbPropertyEntry, в свойстве IsModified которого мы указываем, что состояние объекта изменилось. Это аналогично использованию свойства State класса DbEntityEntry

Обратите внимание на использование метода DbSet.Attach() при перечислении. Этот метод указывает, что объект нужно явно прикрепить в сущностной модели данных, т.к

он был создан вручную, а не загружен из базы данных. Действительно, следующий запрос LINQ to Entities:

фактически извлекает коллекцию идентификаторов имеющую тип IEnumerable<int>, а следующий за ним запрос LINQ to Objects уже создает коллекцию IEnumerable<Customer> в памяти приложения на основе идентификаторов. Как упоминалось ранее, метод AsEnumerable() в контексте использования в Entity Framework позволяет логически разделить один запрос на запрос к базе данных (LINQ to Entities) и простой запрос для работы с коллекциями C# (LINQ to Objects). На выходе, Entity Framework фактически ничего не знает об объектах коллекции customers, поэтому их нужно вручную прикрепить к объекту контекста используя метод Attach().

Явная загрузка (explicit loading)

Последним вариантом загрузки данных в Entity Framework является явная загрузка (explicit loading) данных. Явная загрузка, как и отложенная загрузка, не приводит к загрузке всех связанных данных в первом запросе. Но при этом, в отличие от отложенной загрузки, при вызове навигационного свойства связанного класса, эта загрузка не приводит к автоматическому извлечению связанных данных, вы должны явно вызвать метод Load(), если хотите загрузить связанные данные. Такой тип загрузки может использоваться в следующих случаях:

  • Этот тип загрузки устраняет необходимость отмечать навигационные свойства класса модели как виртуальные. Для вас это может показаться не значительным изменением, но тот факт, что технология доступа к связанным данным при отложенной загрузке требует изменять ваши классы POCO модели, далека от идеальной.

  • Вы можете работать с существующей библиотекой классов, где навигационные свойства не помечены как виртуальные, и вы не можете изменить эту библиотеку.

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

Явная загрузка использует метод DbContext.Entry() для доступа к сущностному объекту, а не свойство типа DbSet. Объект DbEntityEntry, возвращаемый этим методом, дает вам доступ ко всей информации о сущностном типе данных. Помимо обычных свойств модели, этот объект хранит большое количество расширенных настроек сущностных объектов, а также позволяет вызвать метод Load() для вызова явной загрузки. В примере ниже мы используем метод ExplicitLoading(), в котором мы реализовали самый первый пример в этой статье, в котором мы не могли загрузить данные заказов автоматически, т.к. навигационное свойство Customer.Orders не было виртуальным:

В первой части этого примера мы загружаем данные пользователя с идентификатором, равным 2. Затем мы используем явную загрузку, чтобы указать Entity Framework о необходимости загрузить для пользователя связанные с ним заказы

Обратите внимание, что в этом примере используется метод Collection(), которому передается делегат, в котором выбирается навигационное свойство, имеющее тип коллекции. Если навигационное свойство имеет тип ссылки (например, для связи один-к-одному между таблицами), то нужно использовать метод Reference()

После этого используется метод Load() для создания запроса к базе данных.

Если вы теперь вызовете этот метод в основном методе Main() и запустите приложение, то сможете убедиться, что заказы извлекаются корректно. Стоит отметить, что в этом примере будет создано два SQL-запроса к базе данных: при извлечения данных покупателя с CustomerId = 2 и при загрузке заказов для этого покупателя.

Методы Collection() и Reference() класса DbEntityEntry возвращают экземпляры классов DbCollectionEntry и DbReferenceEntry. Эти классы содержат полезное свойство IsLoaded, которое указывает, были ли уже загружены ранее связанные данные. Это свойство позволит оптимизировать вам некоторые запросы, чтобы повторно не извлекать соответствующие данные и его можно использовать не только при явной загрузке, но и при других типах загрузок. Ниже показан соответствующий пример:

В предыдущих примерах при использовании явной загрузки мы указывали, что нужно выбрать все заказы. Что делать, если нужно использовать LINQ-методы для ограничения этой выборки, ведь классы DbCollectionEntry и DbReferenceEntry, объекты которых возвращаются методами Collection() и Reference(), не подходят для использования LINQ-запросов? Для этих целей в этих классах определен вспомогательный метод Query(), который возвращает типизированную сущностным классом коллекцию IQueryable. В примере ниже показано использование этого метода, для ограничения заказов, если количество товаров меньше 5:

Отложенная загрузка (lazy loading)

Отложенная загрузка (lazy loading) заключается в том, что Entity Framework автоматически загружает данные, при этом не загружая связанные данные. Когда потребуются связанные данные Entity Framework создаст еще один запрос к базе данных. В контексте нашего примера это означает, что вы можете, например, загрузить первого заказчика из таблицы Customers и сохранить его в переменной customer. Затем вам может понадобиться узнать, какие заказы связаны с этим покупателем. Напомню, в классе модели Customer у нас определено навигационное свойство Orders. Если вы обратитесь к этому свойству (customer.Orders), то Entity Framework отправит запрос в базу данных на извлечение всех связанных с этим покупателем заказов.

Entity Framework применяет отложенную загрузку, используя динамические прокси-объекты. Вот как это работает. Когда Entity Framework возвращает результаты запроса, он создает экземпляры ваших классов и заполняет их данными, которые были возвращены из базы данных. Entity Framework имеет возможность динамически создавать новый тип во время выполнения, производный от вашего класса модели POCO. Этот новый класс выступает в качестве прокси-объекта для вашего класса POCO и называется динамическим прокси-объектом. Он будет переопределять навигационные свойства вашего класса POCO и включать в себя некоторую дополнительную логику для извлечения данных из базы данных, когда вызывается навигационное свойство. Т.к. динамический прокси-класс является производным от вашего класса POCO, ваше приложение работает непосредственно с классом POCO и не должно знать, что за кулисами создается динамический прокси-объект во время выполнения.

DbContext имеет настройку конфигурации для отложенной загрузки с помощью свойства DbContext.Configuration.LazyLoadingEnabled. Этот параметр включен по умолчанию, поэтому если вы не изменяли значение по умолчанию для него, динамический прокси-объект будет выполнять отложенную загрузку.

Для того, чтобы использовать динамические прокси-объекты, и, следовательно, отложенную загрузку, есть пара условий, которым должен соответствовать ваш класс модели. Если эти условия не выполняются, Entity Framework, не будет создавать динамические прокси-объекты для класса и будет просто возвращать экземпляры вашего класса POCO, которые не могут выполнять отложенную загрузку, а следовательно, взаимодействовать с базой данных:

  • Ваш класс модели должен иметь модификатор доступа public и не должен запрещать наследование (ключевое слово sealed в C#).

  • Навигационные свойства, которые должны обеспечивать отложенную загрузку, должны быть виртуальными (модификатор virtual).

Прежде чем изменять класс модели, давайте посмотрим на поведение приложения без отложенной загрузки. Добавьте новый метод LazyLoading() в ваш класс программы, который имеет следующий код:

Если вы теперь вызовите этот метод в главном методе Main(), то никакие связанные данные не будут загружаться (т.к. мы еще не изучили как вставлять данные с помощью EF, вам нужно будет вручную вставить какие-нибудь данные в таблицу Orders с помощью Visual Studio или SQL Server Management Studio, для тестирования последующих примеров). В этом примере не происходит отложенная загрузка, т.к. наш класс модели Customer не подходит под второе условие – навигационное свойство Customer.Orders не является виртуальным. Давайте изменим это:

Теперь, при запуске приложения Entity Framework создаст динамически прокси-объект для класса Customer и извлечет данные заказов из базы при их запросе. На рисунке ниже наглядно показано какие данные загружает Entity Framework в этом примере:

Отложенная загрузка является очень простой в использовании, потому что используется автоматически. При этом, она является довольно опасной в плане производительности! Что если мы захотим извлечь сначала всех пользователей используя не отложенный запрос, а затем захотим извлечь все заказы, связанные с этими пользователями:

В этом примере создается три запроса, при попытке извлечь все заказы, связанные с заказчиками в коллекции customers. Мы даже включили средство протоколирования в этом запросе, чтобы вы убедились сами. Количество запросов SELECT при извлечении данных из таблицы Orders зависит от количества покупателей в коллекции customers – Entity Framework будет отправлять один запрос на выборку заказов для каждого покупателя. Очевидно, что такой подход является катастрофическим в плане производительности, если в коллекции будет храниться большое число покупателей.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector