Если оператор LINQ для отдельного запроса слишком сложен или тестирование показывает, что производительность оказалась ниже, чем желаемая, тогда данные можно извлекать с использованием низкоуровневого оператора SQL через метод
FromSqlRaw
или
FromSqlInterpolated
класса
DbSet<T>
. Оператором SQL может быть встроенный оператор
SELECT
языка Т-SQL, хранимая процедура или табличная функция. Если запрос является открытым (например, оператор Т-SQL без завершающей точки с запятой), тогда операторы LINQ можно добавлять к вызову метода
FromSqlRaw/FromSqlInterpolated
для дальнейшего определения генерируемого запроса. Полный запрос выполняется на серверной стороне с объединением оператора SQL и кода SQL, сгенерированного операторами LINQ.
Если оператор завершен или содержит код SQL, который не может быть достроен (скажем, задействует общие табличные выражения), то такой запрос все равно выполняется на серверной стороне, но любая дополнительная фильтрация и обработка должна делаться на клиентской стороне как LINQ to Objects. Метод
FromSqlRaw
выполняет запрос в том виде, в котором он набран. Метод
FromSqlInterpolated
применяет интерполяцию строк C# и помещает интерполированные значения в параметры. В следующих тестах (из
CarTests.cs
) демонстрируются примеры использования обоих методов с глобальными фильтрами запросов и без них:
[Fact]
public void ShouldNotGetTheLemonsUsingFromSql
{
var entity = Context.Model.FindEntityType($"{typeof(Car).FullName}");
var tableName = entity.GetTableName;
var schemaName = entity.GetSchema;
var cars = Context.Cars.FromSqlRaw($"Select * from {schemaName}.{tableName}")
.ToList;
Assert.Equal(9, cars.Count);
}
[Fact]
public void ShouldGetTheCarsUsingFromSqlWithIgnoreQueryFilters
{
var entity = Context.Model.FindEntityType($"{typeof(Car).FullName}");
var tableName = entity.GetTableName;
var schemaName = entity.GetSchema;
var cars = Context.Cars.FromSqlRaw($"Select * from {schemaName}.{tableName}")
.IgnoreQueryFilters.ToList;
Assert.Equal(10, cars.Count);
}
[Fact]
public void ShouldGetOneCarUsingInterpolation
{
var carId = 1;
var car = Context.Cars
.FromSqlInterpolated($"Select * from dbo.Inventory where Id = {carId}")
.Include(x => x.MakeNavigation)
.First;
Assert.Equal("Black", car.Color);
Assert.Equal("VW", car.MakeNavigation.Name);
}
[Theory]
[InlineData(1, 1)]
[InlineData(2, 1)]
[InlineData(3, 1)]
[InlineData(4, 2)]
[InlineData(5, 3)]
[InlineData(6, 1)]
public void ShouldGetTheCarsByMakeUsingFromSql(int makeId, int expectedCount)
{
var entity = Context.Model.FindEntityType($"{typeof(Car).FullName}");
var tableName = entity.GetTableName;
var schemaName = entity.GetSchema;
var cars = Context.Cars.FromSqlRaw($"Select * from {schemaName}.{tableName}")
.Where(x => x.MakeId == makeId).ToList;
Assert.Equal(expectedCount, cars.Count);
}
Во
время применения методов
FromSqlRaw/FromSqlInterpolated
действует ряд правил: столбцы, возвращаемые из оператора SQL, должны соответствовать столбцам в модели, должны возвращаться все столбцы для модели, а возвращать связанные данные не допускается.
Методы агрегирования
В EF Core также поддерживаются методы агрегирования серверной стороны (
Мах
,
Min
,
Count
,
Average
и т.д.). Вызовы методов агрегирования можно добавлять в конец запроса LINQ с вызовами
Where
или же сам вызов метода агрегирования может содержать выражение фильтра (подобно
First
и
Single
). Агрегирование выполняется на серверной стороне и из запроса возвращается одиночное значение. Глобальные фильтры запросов оказывают воздействие на методы агрегирования и могут быть отключены с помощью
IgnoreQueryFiltersсе
. В операторы SQL, показанные в этом разделе, были получены с использованием профилировщика SQL Server.
Первый тест (из
CarTests.cs
) просто подсчитывает все записи
Car
в базе данных. Из-за того, что фильтр запросов активен, результатом подсчета будет 9:
[Fact]
public void ShouldGetTheCountOfCars
{
var count = Context.Cars.Count;
Assert.Equal(9, count);
}
Ниже приведен код SQL, который выполнялся:
The executed SQL is shown here:SELECT COUNT(*)
FROM [dbo].[Inventory] AS [i]
WHERE [i].[IsDrivable] = CAST(1 AS bit)
После добавления вызова
IgnoreQueryFilters
метод
Count
возвращает 10 и конструкция
WHERE
удаляется из запроса SQL:
[Fact]
public void ShouldGetTheCountOfCarsIgnoreQueryFilters
{
var count = Context.Cars.IgnoreQueryFilters.Count;
Assert.Equal(10, count);
}
Вот сгенерированный код SQL:
SELECT COUNT(*) FROM [dbo].[Inventory] AS [i]
Следующие тесты (из
CarTests.cs
) демонстрируют метод
Count
с условием
WHERE
. В первом тесте выражение добавляется прямо в вызов метода