• Microsoft .NET
  • LINQ
  • Оптимизация запросов LINQ при помощи DataLoadOptions

Оптимизация запросов LINQ при помощи DataLoadOptions

Одна из крупнейших проблем при работе LINQ с SQL в том, что он запускает запрос SQL для каждого объекта, что сильно влияет на производительность. В этой статье показано, как можно получить все данные при помощи одного запроса SQL.

Основы LINQ

Эта статья предполагает, что у вас есть базовые знания о том, как создаются объекты-сущности при помощи LINQ. В случае если вы не знакомы с основами преобразования LINQ в SQL, можете прочитать мою статью, чтобы понять основные принципы LINQ.

Сущности LINQ клиент, адреса и телефоны

Сначала попытаемся понять, как запросы LINQ работают на самом деле, и затем разберемся, как происходит полный обход. Давайте изучим структуру базы данных ниже, в которой имеется 3 таблицы – customer (клиент), addresses (адреса) и phone (телефон). Между customer и addresses есть отношение один-ко-многим, в то время как между таблицей address и phones есть отношение один-к-одному.

Были созданы три сущности в соответствии со структурой таблицы:
•    ClsCustomerWithAddresses
•    ClsAddresses
•    ClsPhone

Между ними были определены отношения при помощи ‘EntitySet’ и ‘EntityRef’.

Заполнение объектов-сущностей данными из таблицы – процесс из 5 шагов. На первом этапе соединение datacontext создается при помощи строки соединения, создается запрос LINQ, и затем начинается просмотр customer, address и phones.

 

Анализ полных обходов LINQ SQL

Теперь мы знаем, что запрос LINQ выполняется за 5 шагов, попытается выяснить, на каком шаге запрос LINQ в действительности запускает SQL в базу данных. Выполним вышеуказанный код LINQ и проанализируем его выполнение при помощи профилировщика SQL.
Чтобы не поймать много ерунды от сервера SQL, были включены только пакетные события RPC SQL. 

Теперь при выполнении запроса обнаруживаются следующие вещи:
•    Выполнение фактического SQL происходит, когда оператор foreach повторяется на объектах LINQ.
•    Вторая поразительная вещь, которую вы заметите, состоит в том, что для каждой сущности, запускается отдельный запрос к серверу SQL. Например, для customer запускается один запрос, и затем запускаются отдельные запросы для address и phones, чтобы извлечь объект-сущность. Иными словами, присутствует много полных обходов.

 

Предотвращение полных обходов при помощи DataLoadOptions

Можно приказать движку LINQ загружать все объекты при помощи ‘DataLoadOptions’. ниже указаны шаги, требуемые для включения ‘DataLoadOptions’.
Первый шаг – создать класс контекста данных:

DataContext objContext = new DataContext(strConnectionString);

Второй шаг – создать объект ‘DataLoadOption’:

DataLoadOptions objDataLoadOption = new DataLoadOptions();

Путем использования метода LoadWith нужно установить, что customer с address должны загружаться за один запрос SQL.

objDataLoadOption.LoadWith<clsCustomerWithAddresses>
    (clsCustomerWithAddresses => clsCustomerWithAddresses.Addresses);

Каждый объект address имеет объект phone, поэтому было указано, что объекты phone должны загружаться для каждого объекта address за один запрос SQL.

objDataLoadOption.LoadWith<clsAddresses>(clsAddresses => clsAddresses.Phone);

Какой бы вариант загрузки вы ни указали, нужно установить то же самое в объекте контекста данных, используя свойства ‘LoadOptions’.

objContext.LoadOptions = objDataLoadOption;

Наконец, подготовьте ваш запрос:

var MyQuery = from objCustomer in objContext.GetTable<clsCustomerWithAddresses>()
select objCustomer;

Начните проходить в цикле по объектам:

foreach (clsCustomerWithAddresses objCustomer in MyQuery)
{
    Response.Write(objCustomer.CustomerName + "<br>");

    foreach (clsAddresses objAddress in objCustomer.Addresses)
    {
        Response.Write("===Address:- " + objAddress.Address1 + "<br>");
        Response.Write("========Mobile:- " + objAddress.Phone.MobilePhone + "<br>");
        Response.Write("========LandLine:- " + objAddress.Phone.LandLine + "<br>");
    }
}

Ниже приведен полный исходный код для того же самого:

DataContext objContext = new DataContext(strConnectionString);
DataLoadOptions objDataLoadOption = new DataLoadOptions();
objDataLoadOption.LoadWith<clsCustomerWithAddresses>
    (clsCustomerWithAddresses => clsCustomerWithAddresses.Addresses);
objDataLoadOption.LoadWith<clsAddresses>(clsAddresses => clsAddresses.Phone);
objContext.LoadOptions = objDataLoadOption;
var MyQuery = from objCustomer in objContext.GetTable<clsCustomerWithAddresses>()
select objCustomer;

foreach (clsCustomerWithAddresses objCustomer in MyQuery)
{
    Response.Write(objCustomer.CustomerName + "<br>");

    foreach (clsAddresses objAddress in objCustomer.Addresses)
    {
        Response.Write("===Address:- " + objAddress.Address1 + "<br>");
        Response.Write("========Mobile:- " + objAddress.Phone.MobilePhone + "<br>");
        Response.Write("========LandLine:- " + objAddress.Phone.LandLine + "<br>");
    }
}

Теперь при выполнении кода LINQ выполняет только один запрос SQL с правильными объединениями, по сравнению с 3 запросами SQL для каждого объекта, показанными ранее.

Исходный код

Мы также приложили исходный код. Запустите проект и посмотрите, как профилировщик показывает разное выполнение SQL. Сначала можете выполнить пример ‘EntitySet’ и посмотреть, как профилировщик SQL реагирует на одно и то же, и затем выполнить пример с ‘DataLoadOptions’. Скрипт SQL прилагается в другом файле.
Исходный код можно скачать отсюда.