Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Если связанные сущности уже загружены в DbSet<T>, тогда EF Core будет связывать новые экземпляры по навигационным свойствам. Например, если сущности Car загружаются в коллекцию DbSet<Car> и затем связанные сущности Order загружаются в коллекцию DbSet<Order> того же самого экземпляра ApplicationDbContext, то навигационное свойство Car.Orders будет возвращать связанные сущности без повторного запрашивания базы данных.
Многие демонстрируемые здесь методы имеют асинхронные версии. Синтаксис запросов LINQ структурно одинаков, поэтому будут использоваться только синхронные версии.
Состояние сущности
Когда сущность создается за счет чтения данных из базы, значение EntityState устанавливается в Unchanged.
Запросы LINQ
Тип коллекции DbSet<T> реализует (помимо прочих) интерфейс IQueryable<T>, что позволяет применять команды LINQ языка C# для создания запросов, извлекающих информацию из базы данных. Наряду с тем, что все операторы LINQ языка C# доступны для использования с типом коллекции DbSet<T>, некоторые операторы LINQ могут не поддерживаться поставщиком баз данных, а в EF Core появились дополнительные операторы LINQ. Неподдерживаемый оператор LINQ, который невозможно транслировать в язык запросов поставщика баз данных, приведет к генерации исключения времени выполнения, если только он не является последним в цепочке операторов LINQ. Когда неподдерживаемый оператор LINQ находится в самом конце цепочки LINQ, он выполнится на клиентской стороне (в коде С#).
На заметку! Настоящая книга не задумывалась как полный справочник по LINQ, так что в ней приводится не особо много примеров. С дополнительными примерами запросов LINQ можно ознакомиться по ссылке https://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b.
Выполнение запросов LINQ
Вспомните, что при использовании LINQ для запрашивания из базы данных списка сущностей запрос не выполняется до тех пор, пока не начнется проход по результатам запроса, пока запрос не будет преобразован в List<T> (или объект) либо же пока не произойдет привязка запроса к списковому элементу управления (вроде сетки данных). Запрос единственной записи выполняется немедленно в случае применения вызова First(), Single() и т.д.
Нововведением версии EF Core 5 стало то, что в большинстве запросов LINQ можно вызывать метод ToQueryString() для исследования запроса, который выполняется в отношении базы данных. Для разделяемых запросов метод ToQueryString() возвращает только первый запрос, который будет выполняться. В рассматриваемых далее тестах это значение по возможности присваивается переменной (qs), чтобы вы могли изучить запрос во время отладки тестов.
Первый набор тестов (если только специально не указано иначе) находится в файле класса CustomerTests.cs.
Получение всех записей
Чтобы получить все записи из таблицы, просто используйте свойство DbSet<T> на прямую без каких-либо операторов LINQ. Добавьте приведенный ниже тест [Fact]:
[Fact]
public void ShouldGetAllOfTheCustomers()
{
var qs = Context.Customers.ToQueryString();
var customers = Context.Customers.ToList();
Assert.Equal(5, customers.Count);
}
Выделенный полужирным оператор транслируется в следующий код SQL:
SELECT [c].[Id], [c].[TimeStamp], [c].[FirstName], [c].[FullName],
[c].[LastName] FROM [Dbo].[Customers] AS [c]
Тот же самый процесс применяется для сущностей без ключей, подобных модели представления CustomerOrderViewModel, которая сконфигурирован на получение своих данных из представления CustomerOrderView:
modelBuilder.Entity<CustomerOrderViewModel>().HasNoKey()
.ToView("CustomerOrderView", "dbo");
Экземпляр DbSet<T> для моделей представлений предлагает всю мощь запросов DbSet<T> для сущности с ключом. Отличие касается возможностей обновления. Изменения модели представления не могут быть сохранены в базе данных, тогда как изменения сущностей с ключами — могут. Добавьте в файл класса OrderTest.cs показанный далее тест, чтобы продемонстрировать получение данных из представления:
public void ShouldGetAllViewModels()
{
var qs = Context.Orders.ToQueryString();
var orders = Context.Orders.ToList();
Assert.NotEmpty(orders);
Assert.Equal(5,orders.Count);
}
Выделенный полужирным оператор транслируется в следующий код SQL:
SELECT [c].[Color], [c].[FirstName], [c].[IsDrivable], [c].[LastName],
[c].[Make], [c].[PetName] FROM [dbo].[CustomerOrderView] AS [c]
Фильтрация записей
Метод Where() используется для фильтрации записей из DbSet<T>. Несколько вызовов Where() можно плавно объединять в цепочку для динамического построения запроса. Выстроенные в цепочку вызовы Where() всегда объединяются с помощью операции "И". Для объединения условий с применением операции "ИЛИ" необходимо использовать один вызов Where().
Приведенный ниже тест возвращает заказчиков с фамилией, начинающейся с буквы "W" (нечувствительно к регистру символов):
[Fact]
public void ShouldGetCustomersWithLastNameW()
{
IQueryable<Customer> query = Context.Customers
.Where(x => x.PersonalInformation.LastName.StartsWith("W"));
var qs = query.ToQueryString();
List<Customer> customers = query.ToList();
Assert.Equal(2, customers.Count);
}
Запрос LINQ транслируется в следующий код SQL:
SELECT [c].[Id], [c].[TimeStamp], [c].[FirstName], [c].[FullName],
[c].[LastName] FROM [Dbo].[Customers] AS [c]
WHERE [c].[LastName] IS NOT NULL AND ([c].[LastName] LIKE N'W%')
Показанный далее тест возвращает заказчиков с фамилией, начинающейся с буквы "W" (нечувствительно к регистру символов), и именем, начинающимся с буквы "М" (нечувствительно к регистру символов), а также демонстрирует объединение вызовов Where() в цепочку в запросе LINQ:
[Fact]
public void ShouldGetCustomersWithLastNameWAndFirstNameM()
{
IQueryable<Customer> query = Context.Customers
.Where(x => x.PersonalInformation.LastName.StartsWith("W"))
.Where(x => x.PersonalInformation.FirstName.StartsWith("M"));
var qs = query.ToQueryString();
List<Customer> customers = query.ToList();
Assert.Single(customers);
}
Следующий тест возвращает заказчиков с фамилией, начинающейся с буквы "W" (нечувствительно к регистру символов), и именем, начинающимся с буквы "М" (нечувствительно к регистру символов), с применением единственного вызова Where():
[Fact]
public void ShouldGetCustomersWithLastNameWAndFirstNameM()
{
IQueryable<Customer> query = Context.Customers
.Where(x => x.PersonalInformation.LastName.StartsWith("W") &&
x.PersonalInformation.FirstName.StartsWith("M"));
var qs = query.ToQueryString();
List<Customer> customers = query.ToList();
Assert.Single(customers);
}
Оба запроса транслируются в такой код SQL:
SELECT [c].[Id], [c].[TimeStamp], [c].[FirstName], [c].[FullName],
[c].[LastName] FROM [Dbo].[Customers] AS [c]
WHERE ([c].[LastName] IS NOT NULL AND ([c].[LastName] LIKE N'W%'))
AND ([c].[FirstName] IS NOT NULL AND ([c].[FirstName] LIKE N'M%'))
Приведенный ниже тест возвращает заказчиков с фамилией, начинающейся с буквы "W" (нечувствительно к регистру