Отложенное выполнение запроса

32

»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ

Отложенное выполнение запросов означает, что запрос 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.

Последствия отложенного выполнения запросов

Использование преимуществ отложенного выполнения запросов

Одним из преимуществ отложенного выполнения запросов является повышение производительности и в то же время возможность повторного использования ранее определенных запросов. Поскольку запрос выполняется каждый раз, когда производится перечисление возвращаемой им последовательности, его можно определить один раз, а перечислять снова и снова, как только того потребует ситуация. И если поток кода идет по некоторому пути, который не требует просмотра результатов запроса посредством его перечисления, то производительность увеличивается за счет того, что запрос не выполняется.

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

Нарисуйте в воображении один из возможных вариантов интерфейса с фильтрацией, который имеет раскрывающийся список для каждого столбца таблицы заказчиков. Допустим, есть раскрывающийся список для столбца 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);
Применение операции Contains

Как видно, вместо написания запроса так, что город заказчика должен присутствовать в некотором наборе значений, запрос записывается так, что некоторый набор значений содержит город заказчика. В случае вышеуказанного примера создан массив городов по имени cities. Затем в запросе вызывается операция Contains на массиве cities и ей передается город заказчика. Если массив cities содержит город заказчика, будет возвращено значение true в операции Where, что вызовет включение объекта Customer в выходную последовательность.

Пройди тесты
Лучший чат для C# программистов