Выполнение запроса
После создания пользователем запрос LINQ преобразуется в дерево команд. Дерево команд является представлением запроса, совместимым с платформой Entity Framework. Затем дерево команд выполняется в источнике данных. Во время выполнения запроса вычисляются все включенные в него выражения (компоненты запроса), в том числе выражения для материализации запроса.
Выражения выполняются в различные моменты времени. Запросы LINQ всегда выполняются во время прохода по переменной запроса, а не в момент ее создания. Это называется отложенным выполнением. Существует возможность принудительно выполнить запрос немедленно — это может оказаться полезным для кэширования результатов запроса. Этот вопрос обсуждается ниже в данном разделе.
При выполнении запроса в технологии LINQ to Entities некоторые выражения запроса могут выполняться на сервере, а некоторые — локально на клиенте. Вычисление выражения на клиенте происходит раньше, чем выполнение запроса на сервере. Если выражение вычисляется на клиенте, то результат этого вычисления подставляется в запрос вместо выражения, а затем производится выполнение запроса на сервере. Запросы выполняются применительно к источнику данных, поэтому конфигурация источника данных переопределяет правила работы, заданные на клиенте. Например, порядок обработки значений NULL и точность числа зависят от параметров настройки сервера. Все исключения, возникшие на сервере во время выполнения запроса, передаются непосредственно клиенту.
Отложенное выполнение
В переменной запроса хранятся только команды запроса, если он предназначен для возврата последовательности значений. Если запрос не содержит метода, приводящего к его немедленному выполнению, то фактическое выполнение запроса откладывается до завершения обработки переменной запроса в цикле foreach или For Each. Запрос выполняется на сервере каждый раз, когда выполняется проход по переменной запроса в цикле foreach или For Each. Это называется отложенным выполнением. Отложенное выполнение позволяет расширять запросы и объединять несколько запросов в один. В этом случае запрос изменяется, включая в свой состав новые операции, а последующее выполнение отражает эти изменения. Сама переменная запроса никогда не содержит результатов запроса. Это означает, что запрос можно выполнять так часто, как это необходимо. Например, имеется база данных, постоянно обновляемая отдельным приложением. В приложении можно создать один запрос, получающий самые последние данные, и регулярно выполнять его с некоторым интервалом, каждый раз получая различные результаты.
Запросы LINQ to Entities преобразуются в деревья команд на платформе Entity Framework и выполняются в источнике данных, когда осуществляется проход по результатам. На этом этапе ошибки преобразования вызовут исключения, передаваемые клиенту.
Немедленное выполнение
В отличие от отложенного выполнения запросов, возвращающих последовательность значений, запросы, возвращающие одноэлементное значение, выполняются немедленно. Примерами одноэлементных запросов являются Average, Count, First и Max. Они выполняются немедленно, потому что запрос должен создать последовательность, чтобы вычислить одноэлементный результат. Можно также принудительно вызвать немедленное выполнение. Это может оказаться полезным в тех случаях, когда результаты запроса необходимо поместить в кэш. Чтобы немедленно выполнить запрос, который не возвращает одноэлементное значение, можно вызвать метод ToList, ToDictionary или ToArray запроса или переменной запроса. В следующем примере метод ToArray вызывается для немедленного вычисления последовательности с получением массива.
Using AWEntities As New AdventureWorksEntities
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim prodArray As Product() = ( _
From product In products _
Order By product.ListPrice Descending _
Select product).ToArray()
Console.WriteLine("The list price from highest to lowest:")
For Each prod As Product In prodArray
Console.WriteLine(prod.ListPrice)
Next
End Using
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
ObjectQuery<Product> products = AWEntities.Product;
Product[] prodArray = (
from product in products
orderby product.ListPrice descending
select product).ToArray();
Console.WriteLine("Every price from highest to lowest:");
foreach (Product product in prodArray)
{
Console.WriteLine(product.ListPrice);
}
}
Можно также принудительно вызвать выполнение, поместив цикл foreach или For Each сразу после выражения запроса, однако при вызове метода ToList или ToArray все данные будут помещены в кэш в одном объекте коллекции.
Выполнение в хранилище
Как правило, выражения LINQ to Entities вычисляются на сервере, и не следует ожидать, что поведение выражений будет соответствовать семантике среды CLR; вместо этого применяется семантика источника данных. Однако из этого правила существуют исключения, например, когда выражение выполняется на клиенте. Это может привести к непредвиденным результатам, например, когда сервер и клиент находятся в разных часовых поясах.
Некоторые выражения запроса могут выполняться на клиенте. Большинство операций по выполнению запроса выполняются на сервере. Помимо методов, выполняемых применительно к элементам запроса, сопоставленным с источником данных, в запросе часто существуют выражения, которые могут быть выполнены локально. Локальное выполнение выражения запроса возвращает значение, которое можно использовать для выполнения запроса или построения результата.
Некоторые операции всегда выполняются на клиенте, например, привязка значений, вложенные выражения, вложенные запросы из замыканий и материализация объектов в результаты запроса. В итоге получается, что эти элементы (например, значения параметров) не могут обновляться во время выполнения. Анонимные типы могут быть созданы прямо внутри источника данных, однако лучше этого не делать. В источнике данных можно также создавать встроенные группирования, однако не следует это делать в каждом экземпляре. Как правило, лучше не делать никаких предположений относительно объектов, создаваемых на сервере.
В этом разделе описаны сценарии, в которых код выполняется локально на клиенте. Дополнительные сведения о типах выражений, выполняемых локально, см. в разделе Выражения в запросах LINQ to Entities.
Литералы и параметры
Локальные переменные (например, переменная orderID
в приведенном ниже примере) вычисляются на клиенте.
Dim sales As ObjectQuery(Of SalesOrderHeader) = AWEntities.SalesOrderHeader
Dim orderID As Integer = 51987
Dim salesInfo = _
From s In sales _
Where s.SalesOrderID = orderID _
Select s
ObjectQuery<SalesOrderHeader> sales = AWEntities.SalesOrderHeader;
int orderID = 51987;
IQueryable<SalesOrderHeader> salesInfo =
from s in sales
where s.SalesOrderID == orderID
select s;
Параметры методов также вычисляются на клиенте. Например, ниже показан параметр orderID
, передаваемый методу MethodParameterExample
.
Function MethodParameterExample(ByVal orderID As Integer)
Using AWEntities As New AdventureWorksEntities()
Dim sales As ObjectQuery(Of SalesOrderHeader) = AWEntities.SalesOrderHeader
Dim salesInfo = _
From s In sales _
Where s.SalesOrderID = orderID _
Select s
Console.WriteLine("Sales order info:")
For Each sale As SalesOrderHeader In salesInfo
Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue)
Next
End Using
End Function
public static void MethodParameterExample(int orderID)
{
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
ObjectQuery<SalesOrderHeader> sales = AWEntities.SalesOrderHeader;
IQueryable<SalesOrderHeader> salesInfo =
from s in sales
where s.SalesOrderID == orderID
select s;
foreach (SalesOrderHeader sale in salesInfo)
{
Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue);
}
}
}
Приведение литералов на клиенте
Приведение значения null к типу CLR выполняется на клиенте.
Dim contacts As ObjectQuery(Of Contact) = AWEntities.Contact
Dim query = _
From c In contacts _
Where c.EmailAddress = CType(Nothing, String) _
Select c
ObjectQuery<Contact> contacts = AWEntities.Contact;
IQueryable<Contact> query =
from c in contacts
where c.EmailAddress == (string)null
select c;
Приведение к типу, допускающему значение NULL (например, Decimal), выполняется на клиенте.
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
From product In products _
Where product.Weight = CType(23.77, Decimal?) _
Select product
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<Product> query =
from product in products
where product.Weight == (decimal?)23.77
select product;
Конструкторы для литералов
Новые типы CLR, которые могут быть сопоставлены с типами модели EDM, выполняются на клиенте.
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
From product In products _
Where product.Weight = New Decimal(23.77) _
Select product
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<Product> query =
from product in products
where product.Weight == new decimal(23.77)
select product;
Новые массивы также выполняются на клиенте.
Исключения в хранилище
Все ошибки хранилища, обнаруженные во время выполнения запроса, передаются клиенту без сопоставления и обработки.
Конфигурация хранилища
Когда запрос выполняется в хранилище, конфигурация хранилища переопределяет все правила, заданные на клиенте и для всех операций и выражений применяется семантика хранилища. Это может привести к тому, что выполнение будет производиться по-разному в среде CLR и в хранилище для таких областей, как сравнение со значением NULL, упорядочение идентификаторов GUID, точность операций, в которых участвуют неточные типы данных (например, типы с плавающей запятой или DateTime), а также строковые операции. Важно помнить об этом при изучении результатов запроса.
Например, ниже представлены некоторые отличия в работе среды CLR и SQL Server.
SQL Server упорядочивает идентификаторы GUID иначе, чем среда CLR.
Кроме того, возможны различия в точности результатов при работе с типом Decimal в SQL Server. Это происходит из-за требований к фиксированной к точности для десятичного типа данных в SQL Server. Например, арифметическое среднее трех значений типа Decimal — 0,0, 0,0 и 1,0 в памяти на клиенте будет равно 0,3333333333333333333333333333, а в хранилище — 0,333333 (согласно точности по умолчанию для типа Decimal в SQL Server).
Некоторые операции сравнения строк также обрабатываются в SQL Server иначе, чем в среде CLR. Это связано с тем, что порядок сравнения строк зависит от параметров сортировки на сервере.
Вызовы функций или методов, включенных в запрос LINQ to Entities, сопоставляются с каноническими функциями платформы Entity Framework, которые затем преобразуются в Transact-SQL и выполняются в базе данных SQL Server. Бывают ситуации, когда сопоставленные функции работают иначе, чем реализации в библиотеках базовых классов. Например, при вызове метода Contains, StartsWith или EndsWith с пустой строкой в качестве параметра при выполнении в среде CLR возвратит значение true, а при выполнении в SQL Server — значение false. Кроме того, при вызове метода EndsWith также могут вернуться различные результаты, поскольку SQL Server считает равными две строки, различающиеся только конечными пробелами, в то время как с точки зрения среды CLR эти строки не равны. Это продемонстрировано в следующем примере.
Using AWEntities As New AdventureWorksEntities()
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
From p In products _
Where p.Name = "Reflector" _
Select p.Name
Dim q = _
query.Select(Function(c) c.EndsWith("Reflector "))
Console.WriteLine("LINQ to Entities returns: " & q.First())
Console.WriteLine("CLR returns: " & "Reflector".EndsWith("Reflector "))
End Using
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<string> query = from p in products
where p.Name == "Reflector"
select p.Name;
IEnumerable<bool> q = query.Select(c => c.EndsWith("Reflector "));
Console.WriteLine("LINQ to Entities returns: " + q.First());
Console.WriteLine("CLR returns: " + "Reflector".EndsWith("Reflector "));
}