Книги онлайн и без регистрации » Разная литература » Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 297 298 299 300 301 302 303 304 305 ... 407
Перейти на страницу:
class="p1">    => Context.Customers.SingleOrDefault());

}

Предыдущий запрос LINQ транслируется в такой код SQL:

SELECT TOP(2) [c].[Id], [c].[TimeStamp], [c].[FirstName], [c].[FullName],

              [c].[LastName] FROM [Dbo].[Customers] AS [c]

Если никакие данные не возвращаются в случае применения SingleOrDefault(), то результатом будет null, а не исключение:

[Fact]

public void SingleOrDefaultShouldReturnDefaultIfNoneMatch()

{

  // Expression<Func<Customer>> - это лямбда-выражение.

  Expression<Func<Customer, bool>> expression = x => x.Id == 10;

  // Возвращается null, когда ничего не найдено.

  var customer = Context.Customers.SingleOrDefault(expression);

  Assert.Null(customer);

}

Предыдущий запрос LINQ транслируется в такой код SQL:

SELECT TOP(1) [c].[Id], [c].[TimeStamp], [c].[FirstName], [c].[FullName],

              [c].[LastName] FROM [Dbo].[Customers] AS [c]

WHERE [c].[Id] = 10

Глобальные фильтры запросов

Вспомните о наличии для сущности Car глобального фильтра запросов, который отбрасывает данные об автомобилях со значением свойства IsDrivable, равным false:

modelBuilder.Entity<Car>(entity =>

{

  entity.HasQueryFilter(c => c.IsDrivable);

  ...

});

Откройте файл класса CarTests.cs и добавьте показанный далее тест (все тесты в последующих разделах находятся в СаrTests.cs, если не указано иначе):

[Fact]

public void ShouldReturnDrivableCarsWithQueryFilterSet()

{

  IQueryable<Car> query = Context.Cars;

  var qs = query.ToQueryString();

  var cars = query.ToList();

  Assert.NotEmpty(cars);

  Assert.Equal(9, cars.Count);

}

Также вспомните, что в процессе инициализации данных были созданы 10 записей об автомобилях,из которых один установлен как неуправляемый. При запуске запроса применяется глобальный фильтр запросов и выполняется следующий код SQL:

SELECT [i].[Id], [i].[Color], [i].[IsDrivable], [i].[MakeId], [i].[PetName],

       [i].[TimeStamp] FROM [dbo].[Inventory] AS [i]

WHERE [i].[IsDrivable] = CAST(1 AS bit)

На заметку! Как вскоре будет показано, глобальные фильтры запросов также применяются при загрузке связанных сущностей и при использовании методов FromSqlRaw() и FromSqlInterpolated().

Отключение глобальных фильтров запросов

Чтобы отключить глобальные фильтры запросов для сущностей в запросе, добавьте к запросу LINQ вызов метода IgnoreQueryFilters(). Он заблокирует все фильтры для всех сущностей в запросе. Если есть несколько сущностей с глобальными фильтрами запросов и некоторые фильтры сущностей нужны, тогда потребуется поместить их в методы Where() оператора LINQ. Добавьте в файл класса CarTests.cs приведенный ниже тест, который отключает фильтр запросов и возвращает все записи:

[Fact]

public void ShouldGetAllOfTheCars()

{

  IQueryable<Car> query = Context.Cars.IgnoreQueryFilters();

  var qs = query.ToQueryString();

  var cars = query.ToList();

  Assert.Equal(10, cars.Count);

}

Как и можно было ожидать, в сгенерированном коде SQL больше нет конструкции WHERE, устраняющей записи для неуправляемых автомобилей:

SELECT [i].[Id], [i].[Color], [i].[IsDrivable], [i].[MakeId], [i].[PetName],

       [i].[TimeStamp] FROM [dbo].[Inventory] AS [i]

Фильтры запросов для навигационных свойств

Помимо глобального фильтра запросов для сущности Car был добавлен фильтр запросов к свойству CarNavigation сущности Order:

modelBuilder.Entity<Order>().HasQueryFilter(e => e.CarNavigation!.IsDrivable);

Чтобы увидеть его в действии, добавьте в файл класса OrderTests.cs следующий тест:

[Fact]

public void ShouldGetAllOrdersExceptFiltered()

{

    var query = Context.Orders.AsQueryable();

    var qs = query.ToQueryString();

    var orders = query.ToList();

    Assert.NotEmpty(orders);

    Assert.Equal(4,orders.Count);

}

Вот сгенерированный код SQL:

SELECT [o].[Id], [o].[CarId], [o].[CustomerId], [o].[TimeStamp]

FROM [Dbo].[Orders] AS [o]

INNER JOIN (

    SELECT [i].[Id], [i].[IsDrivable]

    FROM [dbo].[Inventory] AS [i]

    WHERE [i].[IsDrivable] = CAST(1 AS bit)rn) AS [t]

    ON [o].[CarId] = [t].[Id]

WHERE [t].[IsDrivable] = CAST(1 AS bit)

Поскольку навигационное свойство CarNavigation является обязательным, механизм трансляции запросов использует конструкцию INNER JOIN, исключая записи Order, где Car соответствует неуправляемому автомобилю. Для возвращения всех записей добавьте в запрос LINQ вызов IgnoreQueryFilters().

Энергичная загрузка связанных данных

В предыдущей главе объяснялось, что сущности, которые связаны через навигационные свойства, могут создаваться в одном запросе с применением энергичной загрузки. Метод Include() указывает соединение со связанной сущностью, а метод ThenInclude() используется для последующих соединений. Оба метода будут задействованы в рассматриваемых далее тестах. Как упоминалось ранее, когда методы Include()/ThenInclude() транслируются в SQL, для обязательных отношений применяется внутреннее соединение, а для необязательных — левое соединение.

Поместите в файл класса CarTests.cs следующий тест, чтобы продемонстрировать одиночный вызов Include():

[Fact]

public void ShouldGetAllOfTheCarsWithMakes()

{

 IIncludableQueryable<Car, Make?> query =

  Context.Cars.Include(c => c.MakeNavigation);

  var queryString = query.ToQueryString();

  var cars = query.ToList();

  Assert.Equal(9, cars.Count);

}

Тест добавляет к результатам свойство MakeNavigation, выполняя внутреннее соединение с помощью показанного ниже кода SQL. Обратите внимание, что глобальный фильтр запросов действует:

SELECT [i].[Id], [i].[Color], [i].[IsDrivable], [i].[MakeId], [i].[PetName],

       [i].[TimeStamp], [m].[Id], [m].[Name], [m].[TimeStamp]

FROM [dbo].[Inventory] AS [i]

INNER JOIN [dbo].[Makes] AS [m] ON [i].[MakeId] = [m].[Id]

WHERE [i].[IsDrivable] = CAST(1 AS bit)

Во втором тесте используется два набора связанных данных. Первый — это получение информации Make (как и в предыдущем тесте), а второй — получение сущностей Order и затем присоединенных к ним сущностей Customer. Полный тест также отфильтровывает записи Car, для которых есть записи Order. Для необязательных отношений генерируются левые соединения:

[Fact]

public void ShouldGetCarsOnOrderWithRelatedProperties()

{

  IIncludableQueryable<Car, Customer?> query = Context.Cars

    .Where(c => c.Orders.Any())

    .Include(c => c.MakeNavigation)

    .Include(c => c.Orders).ThenInclude(o => o.CustomerNavigation);

  var queryString = query.ToQueryString();

  var cars = query.ToList();

  Assert.Equal(4, cars.Count);

  cars.ForEach(c =>

  {

    Assert.NotNull(c.MakeNavigation);

    Assert.NotNull(c.Orders.ToList()[0].CustomerNavigation);

  });

}

Вот сгенерированный запрос:

SELECT [i].[Id], [i].[Color], [i].[IsDrivable], [i].[MakeId], [i].[PetName],

       [i].[TimeStamp], [m].[Id], [m].[Name], [m].[TimeStamp], [t0].[Id],

       [t0].[CarId], [t0].[CustomerId], [t0].[TimeStamp], [t0].[Id0],

       [t0].[TimeStamp0], [t0].[FirstName], [t0].[FullName],

    [t0].[LastName], [t0].[Id1]

FROM [dbo].[Inventory] AS [i]

     INNER JOIN [dbo].[Makes] AS [m] ON [i].[MakeId]=[m].[Id]

     LEFT JOIN(SELECT [o].[Id], [o].[CarId], [o].[CustomerId], [o].[TimeStamp],

1 ... 297 298 299 300 301 302 303 304 305 ... 407
Перейти на страницу:

Комментарии
Минимальная длина комментария - 20 знаков. В коментария нецензурная лексика и оскорбления ЗАПРЕЩЕНЫ! Уважайте себя и других!
Комментариев еще нет. Хотите быть первым?