Подтвердить что ты не робот

Почему Entity Framework занимает 30 секунд, чтобы загружать записи, когда сгенерированный запрос занимает только половину секунды?

Время выполнения ниже 30 секунд в первый раз и 25 секунд в следующий раз, когда я выполняю один и тот же набор кода. При просмотре в SQL Profiler я сразу вижу логин, а затем он сидит там около 30 секунд. Затем, как только выполняется инструкция select, приложение завершает команду ToList. Когда я запускаю сгенерированный запрос из Management Studio, запрос базы данных занимает всего около 400 мс. Он возвращает 14 строк и 350 столбцов. Похоже, что время, затрачиваемое на преобразование результатов базы данных в сущности, настолько мало, что это не примечательно.

Итак, что происходит за 30 секунд до вызова базы данных?

Если структура сущности такая медленная, мы не можем ее использовать. Есть ли что-то, что я делаю неправильно или что-то, что я могу изменить, чтобы резко ускорить это?

UPDATE: Хорошо, если я использую Скомпилированный запрос, первый раз он занимает 30 секунд, а второй раз занимает 1/4 секунды. Можно ли что-то сделать, чтобы ускорить первый вызов?

using (EntitiesContext context = new EntitiesContext()) 
{ 
    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 
    var groupQuery = (from g in context.Groups.Include("DealContract") 
                    .Include("DealContract.Contracts") 
                    .Include("DealContract.Contracts.AdvertiserAccountType1") 
                    .Include("DealContract.Contracts.ContractItemDetails") 
                    .Include("DealContract.Contracts.Brands") 
                    .Include("DealContract.Contracts.Agencies") 
                    .Include("DealContract.Contracts.AdvertiserAccountType2") 
                    .Include("DealContract.Contracts.ContractProductLinks.Products") 
                    .Include("DealContract.Contracts.ContractPersonnelLinks") 
                    .Include("DealContract.Contracts.ContractSpotOrderTypes") 
                    .Include("DealContract.Contracts.Advertisers") 
                where g.GroupKey == 6 
                select g).OfType<Deal>(); 
    sw.Stop(); 
    var queryTime = sw.Elapsed; 
    sw.Reset(); 
    sw.Start(); 
    var groups = groupQuery.ToList(); 
    sw.Stop(); 
    var executeTime = sw.Elapsed; 
} 
4b9b3361

Ответ 1

У меня была такая же проблема, мой запрос занимал 40 секунд.

Я обнаружил, что проблема связана с функциями .Include("table_name"). Чем больше у меня было, тем хуже было. Вместо этого я поменял свой код на Lazy Загрузите все данные, которые мне нужны сразу после запроса, это сбило общее время примерно на 1,5 секунды с 40 секунд. Насколько я знаю, это выполняет то же самое.

Итак, для вашего кода это будет примерно так:

var groupQuery = (from g in context.Groups
            where g.GroupKey == 6 
            select g).OfType<Deal>(); 

var groups = groupQuery.ToList();

foreach (var g in groups)
{
    // Assuming Dealcontract is an Object, not a Collection of Objects
    g.DealContractReference.Load();
    if (g.DealContract != null)
    {
        foreach (var d in g.DealContract)
        {
            // If the Reference is to a collection, you can just to a Straight ".Load"
            //  if it is an object, you call ".Load" on the refence instead like with "g.DealContractReference" above
            d.Contracts.Load();
            foreach (var c in d.Contracts)
            {
                c.AdvertiserAccountType1Reference.Load();
                // etc....
            }
        }
    }
}

Кстати, если бы вы добавили эту строку кода над запросом в свой текущий код, это снизит время до примерно 4-5 секунд (по-прежнему слишком сильно зависит от моего варианта). Из того, что я понимаю, MergeOption.NoTracking отключает большую часть накладных расходов на отслеживание для обновления и вставки материала обратно в базу данных:

context.groups.MergeOption = MergeOption.NoTracking;

Ответ 2

Это из-за Include. Я предполагаю, что вы хотите загружать в память много объектов. Для создания объектов С#, соответствующих вашим объектам db, требуется много времени.

Моя рекомендация для вас - попытаться ленить нагрузку только на нужные вам данные.

Ответ 3

Единственный способ сделать начальную компиляцию запроса быстрее, чем я знаю, - сделать запрос менее сложным. Документация MSDN на соображения производительности для Entity Framework и Скомпилированные запросы не указывают, что есть способ сохранить скомпилированный запрос для использования в другом сеансе выполнения приложения.

Я бы добавил, что мы обнаружили, что наличие большого количества включений может сделать выполнение запросов медленнее, чем меньше Включает и больше загружает связанные дочерние объекты. Для поиска нужного носителя требуется некоторая пробная версия и ошибка.

Тем не менее, я должен спросить, действительно ли вам нужно каждое свойство каждой сущности, которую вы здесь включаете. Мне кажется, что в этом запросе имеется большое количество разных типов сущностей, поэтому материализация их вполне может быть довольно дорогостоящей. Если вы просто пытаетесь получить табличные результаты, которые вы не собираетесь обновлять, проецируя (относительно) меньшее количество полей, которые вам действительно нужны, плоский, анонимный тип должен быть значительно быстрее по разным причинам. Кроме того, это освобождает вас от необходимости беспокоиться о загруженности, вызывая Load/IsLoaded и т.д.

Вы можете ускорить создание исходного представления, предварительно скомпилировав представления сущностей. Для этого есть документация по MSDN. Но так как вы платите эту стоимость в момент выполнения первого запроса, ваш тест с помощью простого запроса показывает, что он работает в районе 2 секунд для вас. Приятно сказать, что 2 секунды, но это ничего не спасет.

Ответ 4

Для запуска EF требуется некоторое время. Он нуждается в сборке метаданных из xml и, возможно, генерирует объекты, используемые для сопоставления. Так что для запуска требуется несколько секунд, я не думаю, что есть способ обойти это, за исключением того, что вы никогда не перезапускаете свою программу.