var query = Context.Cars.AsQueryable();
query = query.Where(x=>x.Color == "Yellow");
var cars = query.ToList();
Запросы с одной записью (как в случае применения
First()/FirstOrDefault()
) выполняются немедленно при вызове действия (такого как
FirstOrDefault()
), а операторы создания, обновления и удаления выполняются немедленно, когда запускается метод
DbContext.SaveChanges()
.
Смешанное выполнение на клиентской и серверной сторонах
В предшествующих версиях EF Core была введена возможность смешивания выполнения на стороне сервера и на стороне клиента. Это означало, что где-то в середине оператора LINQ можно было бы вызвать функцию C# и по существу свести на нет все преимущества, описанные в предыдущем разделе. Часть до вызова функции C# выполнится на стороне сервера, но затем все результаты (в данной точке запроса) доставляются на сторону клиента и остаток запроса будет выполнен как LINQ to Objects. В итоге возможность смешанного выполнения привнесла больше проблем, нежели решила, и в выпуске EF Core 3.1 такая функциональность была изменена. Теперь выполнять на стороне клиента можно только последний узел оператора LINQ.
Сравнение отслеживаемых и неотслеживаемых запросов
При чтении информации из базы данных в экземпляр
DbSet<T>
сущности (по умолчанию) отслеживаются компонентом
ChangeTracker
, что обычно и требуется в приложении. Как только начинается отслеживание экземпляра компонентом
ChangeTracker
, любые последующие обращения к базе данных за тем же самым элементом (на основе первичного ключа) будут приводить к обновлению элемента, а не к его дублированию.
Однако временами из базы данных необходимо получать данные, отслеживать которые с помощью
ChangeTracker
нежелательно. Причина может быть связана с производительностью (отслеживание первоначальных и текущих значений для крупных наборов записей увеличивает нагрузку на память) либо же с тем фактом, что извлекаемые записи никогда не будут изменяться частью приложения, которая нуждается в этих данных.
Чтобы загрузить экземпляр
DbSet<T>
, не помещая данные в
ChangeTracker
, добавьте к оператору LINQ вызов
AsNoTracking()
, который указывает EF Core о необходимости извлечения данных без их помещения в
ChangeTracker
. Например, для загрузки записи
Car
без ее добавления в
ChangeTracker
введите следующий код:
public virtual Car? FindAsNoTracking(int id)
=> Table.AsNoTracking().FirstOrDefault(x => x.Id == id);
Преимущество показанного кода в том, что он не увеличивает нагрузку на память, но с ним связан и недостаток: дополнительные вызовы для извлечения той же самой записи
Car
создадут ее добавочные копии. За счет потребления большего объема памяти и чуть более длительного выполнения запрос можно модифицировать, чтобы гарантировать наличие только одного экземпляра несопоставленной сущности
Car
:
public virtual Car? FindAsNoTracking(int id)
=> Table.AsNoTrackingWithIdentityResolution().FirstOrDefault(x => x.Id == id);
Важные функциональные средства EF Core
Многие функциональные средства из EF 6 были воспроизведены в EF Core, а с каждым выпуском добавляются новые возможности. Множество средств в EF Core усовершенствовано как с точки зрения функциональности, так и в плане производительности. В дополнение к средствам, воспроизведенным из EF 6, инфраструктура EF Core располагает многочисленными новыми возможностями, которые в предыдущей версии отсутствовали. Ниже приведены наиболее важные функциональные средства инфраструктуры EF Core (в произвольном порядке).
На заметку! Фрагменты кода в текущем разделе взяты прямо из завершенной библиотеки доступа к данным
AutoLot
, которая будет построена в следующей главе.
Обработка значений, генерируемых базой данных
Помимо отслеживания изменений и генерации запросов SQL из LINQ существенным преимуществом использования EF Core по сравнению с низкоуровневой инфраструктурой ADO.NET является гладкая обработка значений, генерируемых базой данных. После добавления или обновления сущности исполняющая среда EF Core запрашивает любые данные, генерируемые базой, и автоматически обновляет сущность с применением корректных значений. При работе с низкоуровневой инфраструктурой ADO.NET это пришлось бы делать самостоятельно.
Например, таблица
Inventory
имеет целочисленный первичный ключ, который определяется в SQL Server как столбец
Identity
. Столбцы
Identity
заполняются СУБД SQL Server уникальными числами (из последовательности) при добавлении записи и не могут обновляться во время обычных обновлений (исключая особый случай
IDENTITY_INSERT
). Кроме того, таблица
Inventory
содержит столбец
TimeStamp
для проверки параллелизма. Проверка параллелизма рассматривается далее, а пока достаточно знать, что столбец
TimeStamp
поддерживается SQL Server и обновляется при любом действии добавления или редактирования.
В качестве примера возьмем добавление новой записи
Car
в таблицу
Inventory
. В приведенном ниже коде создается новый экземпляр
Car
, который добавляется к экземпляру
DbSet<Car>
класса, производного от
DbContext
, и вызывается метод
SaveChanges()
для сохранения данных:
var car = new Car
{
Color = "Yellow",
MakeId = 1,
PetName = "Herbie"
};
Context.Cars.Add(car);
Context.SaveChanges();
При выполнении метода
SaveChanges()
в таблицу вставляется новая запись, после чего исполняющей среде EF Core возвращаются значения
Id
и
TimeStamp
из таблицы, причем свойства сущности обновляются надлежащим образом: