Отложенное выполнение запроса
32LINQ --- LINQ to DataSet и SQL --- Отложенное выполнение запроса
»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ
Отложенное выполнение запросов означает, что запрос LINQ любого типа — LINQ to SQL, LINQ to XML или LINQ to Object — может и не выполняться в точке его определения. Рассмотрим, к примеру, следующий запрос:
IQueryable<Customer> custs = from с in db.Customers
where c.Country == "UK"
select c;
Запрос базы данных на самом деле не выполняется при выполнении этого оператора; он просто определяется и присваивается переменной custs. Запрос не будет выполнен до тех пор, пока не начнется перечисление последовательности custs.
Последствия отложенного выполнения запросов
Одним из последствий отложенного выполнения запросов является то, что запрос может содержать ошибки, которые вызовут исключения, но только во время действительного выполнения запросов, а не во время его определения.
Это может здорово запутать, когда вы проходите отладчиком через оператор запроса, и все идет хорошо, а затем, намного дальше в коде, при перечислении последовательности запроса генерируется исключение. Или, возможно, на последовательности запроса вызывается другая операция, что дает в результате новую последовательность, которая немедленно перечисляется.
Другим последствием является то, что поскольку запрос SQL выполняется при перечислении последовательности запроса, многократное ее перечисление ведет к многократному выполнению запроса SQL.
Это определенно может плохо отразиться на производительности. Чтобы предотвратить это, нужно вызвать на последовательности одну из стандартных операций запросов преобразования — ToArray<T>, ToList<T>, ToDictionary<T, К> или ToLookup<T, К>. Каждая из этих операций преобразует последовательность, на которой она вызвана, в структуру данных указанного типа, что в результате приведет к кэшированию результата. Перечисление этой новой структуры данных можно выполнять многократно, не вызывая повторного выполнения SQL-запроса с потенциальной вероятностью изменения результатов.
Использование преимуществ отложенного выполнения запросов
Одним из преимуществ отложенного выполнения запросов является повышение производительности и в то же время возможность повторного использования ранее определенных запросов. Поскольку запрос выполняется каждый раз, когда производится перечисление возвращаемой им последовательности, его можно определить один раз, а перечислять снова и снова, как только того потребует ситуация. И если поток кода идет по некоторому пути, который не требует просмотра результатов запроса посредством его перечисления, то производительность увеличивается за счет того, что запрос не выполняется.
Другая выгода от отложенного выполнения запросов заключается в том, что по-скольку запрос на самом деле не выполняется при его определении, можно добавлять дополнительные операции программно по мере необходимости. Представьте приложение, которое позволяет пользователю запрашивать информацию о заказчиках. Также представьте, что пользователь может фильтровать запрошенный список заказчиков!
Нарисуйте в воображении один из возможных вариантов интерфейса с фильтрацией, который имеет раскрывающийся список для каждого столбца таблицы заказчиков. Допустим, есть раскрывающийся список для столбца City и другой — для столбца Country. Каждый раскрывающийся список содержит все города и все страны из всех записей Customer в базе данных. В вершине каждого списка есть опция [ALL], принятая по умолчанию для соответствующего ему столбца данных. Если пользователь не изменяет состояние любого из этих раскрывающихся списков, никакой дополнительной конструкции where к запросу для соответствующего столбца не добавляется.
Ниже приведен пример программного построения запроса для такого интерфейса:
Northwind db = new Northwind(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
AttachDbFilename=C:\Northwind.mdf;
Integrated Security=True");
// Представьте, что значения, приведенные ниже, не закодированы жестко, а вместо
// этого получаются из выбранных значений некоторых раскрывающихся списков
string dropDownListCityValue = "Cowes";
string dropDownListCountryValue = "UK";
IQueryable<Customer> custs = from c in db.Customers select c;
if (!dropDownListCityValue.Equals("[ALL]"))
{
custs = from c in custs
where c.City == dropDownListCityValue
select c;
}
if (!dropdownListCountryValue.Equals("[ALL]"))
{
custs = from c in custs
where c.Country == dropDownListCountryValue
select c;
}
foreach (Customer cust in custs)
MessageBox.Show(String.Format("{0} - {1} - {2}",
cust.CompanyName, cust.City, cust.Country));
В этом коде эмулируется получение выбранного пользователем города и страны из раскрывающихся списков. Если они не установлены в "[ALL]" к запросу добавляется дополнительная операция where. Поскольку запрос на самом деле не выполняется до того, как начнется перечисление возвращенной им последовательности, его можно строить по частям.
Взглянем на результат работы кода:
Обратите внимание, что поскольку указано, что выбран город Cowes и страна UK, то и получены записи о заказчиках из Cowes, United Kingdom. здесь выполняется единственный оператор SQL. Поскольку выполнение запроса отложено до момента, когда в нем возникнет необходимость, можно продолжить добавлять условия к запросу, чтобы дополнительно ограничить его или упорядочить результат, без каких-либо затрат на множественные запросы SQL.
Для другого теста в следующем примере значение переменной dropdownListCityValue изменяется на "[ALL]", чтобы посмотреть, как будет выглядеть выполняющийся оператор запроса SQL и каковы будут полученные результаты. Поскольку в качестве города по умолчанию указан "[ALL]", запрос SQL не должен ограничивать результирующий набор никаким городом:
...
string dropdownListCityValue = "[ALL]";
string dropdownListCountryValue = "UK";
IQueryable<Customer> custs = (from c in db.Customers
select c);
if (!dropdownListCityValue.Equals("[ALL]"))
{
custs = from c in custs
where c.City == dropdownListCityValue
select c;
}
if (!dropdownListCountryValue.Equals("[ALL]"))
{
custs = from c in custs
where c.Country == dropdownListCountryValue
select c;
}
...
Здесь видно, что в части where оператора запроса SQL больше не указан город, что и требовалось. Также в выводе результата теперь присутствуют заказчики из разных городов Соединенного Королевства.
Конечно, всегда можно добавить вызов стандартных операций запросов ToArrау<Т>, ToList<T>, ToDictionary<T, К> или ToLookup<T, К>, чтобы принудительно выполнить запрос тогда, когда это необходимо.
Получение SQL-конструкции IN с помощью операции Contains
Одной из возможностей операций SQL, которых недоставало ранним воплощениям LINQ to SQL, была способность выполнять SQL-конструкцию IN — вроде той, что представлена в следующем SQL-запросе:
SELECT * FROM Customers
WHERE (City IN ('London', 'Madrid'))
Для решения этой проблемы была добавлена операция Contains. Но она работает в направлении, противоположном тому, чего можно было ожидать от реализации SQL-конструкции IN. Эта конструкция позволяет указать, что некоторый член сущностного класса должен быть в (IN) некотором наборе значений. Вместо этого она работает в обратной манере. Взглянем на пример ниже, в котором демонстрируется применение операции Contains:
// Используйте свою строку подключения
Northwind db = new Northwind(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
AttachDbFilename=C:\Northwind.mdf;
Integrated Security=True");
// Используем протоколирование
TextBoxWriter txb = new TextBoxWriter(txt_log);
db.Log = txb;
string[] cities = { "London", "Madrid" };
IQueryable<Customer> custs = db.Customers.Where(c => cities.Contains(c.City));
string s = "";
foreach (Customer cust in custs)
s += String.Format("\n{0} - {1}", cust.CustomerID, cust.City);
MessageBox.Show(s);
Как видно, вместо написания запроса так, что город заказчика должен присутствовать в некотором наборе значений, запрос записывается так, что некоторый набор значений содержит город заказчика. В случае вышеуказанного примера создан массив городов по имени cities. Затем в запросе вызывается операция Contains на массиве cities и ей передается город заказчика. Если массив cities содержит город заказчика, будет возвращено значение true в операции Where, что вызовет включение объекта Customer в выходную последовательность.