Как оптимизировать этот запрос LINQ для Visual Studio? - PullRequest
3 голосов
/ 24 октября 2010

У меня есть один гигантский сложный запрос LINQ to SQL, который мне нужно как-то оптимизировать, потому что фоновый компилятор C # полностью загружает процессор, и я не могу нормально набирать или редактировать мой .cs файл в Visual Studio 2010 (каждая букваособенно если IntelliSense хочет всплыть, ужасно отстает).

Виноват в этом:

var custFVC = 
    (from cfvc in customer.CustomerFrameVariationCategories
    let lastValue = cfvc.CustomerFrameVariationCategoryValueChanges.Where(cfvcvc => cfvcvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(cfvcvc2 => cfvcvc2.ChangeDateTime).FirstOrDefault() ?? new CustomerFrameVariationCategoryValueChange()
    let lastValue2 = cfvc.FrameVariationCategory.FrameVariation.Frame.FrameValueChanges.Where(fvc => fvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fvc2 => fvc2.ChangeDateTime).FirstOrDefault() ?? new FrameValueChange()
    let lastValue3 = cfvc.FrameVariationCategory.FrameVariationCategoryValueChanges.Where(fvcvc => fvcvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fvcvc2 => fvcvc2.ChangeDateTime).FirstOrDefault() ?? new FrameVariationCategoryValueChange()
    let lastValue4 = cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Any(fvm => (fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleValueChange()).IsActive == false)
    where lastValue.IsActive == true
    orderby cfvc.FrameVariationCategory.FrameVariation.Frame.Name, cfvc.FrameVariationCategory.Category.Name, cfvc.FrameVariationCategory.FrameVariation.Name
    select new
    {
       cfvc.Id,
       cfvc.FrameVariationCategory,
       lastValue.CoverCoefficient,
       lastValue.NeiserNet,
       PlywoodName = lastValue2.Plywood.Name,
       FrameIsActive = lastValue2.IsActive,
       OwnCost = cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Sum(fvm => // sum all frame variation modules
          (lastValue4 ? 0 : fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime) // if module not active then 0
          .OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault().Porolone) + // otherwise get Porolone
           fvm.FrameModule.FrameModuleComponents.Sum(fmc => // add to Porolone sum of all module components
           (fmc.Article.ArticleDetails.Any() ? fmc.Article.ArticleDetails.Sum(ad => // if any article details then use A*L*W*T instead of Amount
           WindowExcel.MultiplyArticleDetailValues(ad.ArticleDetailValueChanges.Where(advc => advc.ChangeDateTime <= this.SelectedDateTime)
          .OrderByDescending(advc2 => advc2.ChangeDateTime).FirstOrDefault() ?? new ArticleDetailValueChange())) : 
           WindowExcel.GetModuleComponentAmount(fmc.FrameModuleComponentValueChanges.Where(fmcvc => fmcvc.ChangeDateTime <= this.SelectedDateTime) // no details = get amount
             .OrderByDescending(fmcvc2 => fmcvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleComponentValueChange())) * // times article values
           WindowExcel.MultiplyArticleValues(fmc.Article.ArticleValueChanges.Where(avc => avc.ChangeDateTime <= this.SelectedDateTime)
           .OrderByDescending(avc2 => avc2.ChangeDateTime).FirstOrDefault() ?? new ArticleValueChange()))),
       Cubes = cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Sum(fvm => (fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime && fmvc.IsActive == true).OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleValueChange()).Cubes),
       lastValue3.CoverNet,
       lastValue3.CoverGarbage,
       lastValue3.CoverGross,
       lastValue3.CoverPrice,
       lastValue3.BackgroundNet,
       lastValue3.BackgroundGarbage,
       lastValue3.BackgroundGross,
       lastValue3.BackgroundPrice,
       FVCIsActive = lastValue3.IsActive,
       FrameModuleAnyNonActive = lastValue4
    }).ToList();

Самая большая проблема здесь - OwnCost, все до и после Visual Studioможет справиться.Я не хочу отключать фоновую компиляцию (функция, которая проверяет ошибки времени компиляции перед фактической компиляцией), я не хочу создавать хранимую процедуру.Я не могу отключить этот код в отдельный класс / метод, потому что LINQ DataContext не может быть передан (насколько я знаю - также примите во внимание, что переменная контекста находится внутри оператора using).

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

Как я могупереместить (или оптимизировать) OwnCost или весь запрос из текущего .cs файла, или, возможно, разделить его на метод в том же файле (может помочь фоновый компилятор) или «что-то» ...?

Ответы [ 7 ]

5 голосов
/ 25 октября 2010

Мой первый инстинкт: вы пытаетесь заставить LINQ to SQL выполнять работу с хранимой процедурой.Но это может быть неправильно;довольно сложно сказать, было бы возможно для хранимой процедуры сделать это.

Мой второй инстинкт заключается в том, что можно разделить вычисление OwnCost на функцию, так что этот запрос простосодержит

OwnCost = cfvc.Select(CalculateOwnCost)

Мой третий инстинкт, когда я вижу, что расчет включает в себя объект WindowExcel, - это бежать от крика, но я собираюсь сделать пару глубоких вдохов и спросить, действительно ли вывзаимодействие с Excel в контексте этого запроса, и может ли это быть источником проблем?

Редактировать

Чтобы разбить вычисление OwnCost на его собственныесделайте что-то вроде этого:

public decimal CalculateOwnCost(CustomerFrameVariationCategory cvfc)
{
   return cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Sum(fvm => // sum all frame variation modules
                 (lastValue4 ? 0 : fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime) // if module not active then 0
                 .OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault().Porolone) + // otherwise get Porolone
                  fvm.FrameModule.FrameModuleComponents.Sum(fmc => // add to Porolone sum of all module components
                  (fmc.Article.ArticleDetails.Any() ? fmc.Article.ArticleDetails.Sum(ad => // if any article details then use A*L*W*T instead of Amount
                  WindowExcel.MultiplyArticleDetailValues(ad.ArticleDetailValueChanges.Where(advc => advc.ChangeDateTime <= this.SelectedDateTime)
                 .OrderByDescending(advc2 => advc2.ChangeDateTime).FirstOrDefault() ?? new ArticleDetailValueChange())) : 
                  WindowExcel.GetModuleComponentAmount(fmc.FrameModuleComponentValueChanges.Where(fmcvc => fmcvc.ChangeDateTime <= this.SelectedDateTime) // no details = get amount
                 .OrderByDescending(fmcvc2 => fmcvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleComponentValueChange())) * // times article values
                  WindowExcel.MultiplyArticleValues(fmc.Article.ArticleValueChanges.Where(avc => avc.ChangeDateTime <= this.SelectedDateTime)
                  .OrderByDescending(avc2 => avc2.ChangeDateTime).FirstOrDefault() ?? new ArticleValueChange()))),
              Cubes = cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Sum(fvm => (fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime && fmvc.IsActive == true).OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleValueChange()).Cubes)
}

Это предполагает, что CustomerFrameVariationCategories представляет собой совокупность CustomerFrameVariationCategory объектов, а OwnCost является decimal.

После того, как выСделайте это, ваш исходный запрос может просто включать Select, который я показал выше - вы также можете написать его как

OwnCost = cfvc.Select(x => CalculateOwnCost(x))

, если вам будет удобнее (меня достаточно поругал Resharperна этот момент тЯ пришел, чтобы принять это, но это вопрос вкуса).

Нет причины, по которой вы не можете далее разложить некоторые промежуточные выражения в этом запросе на их собственные функции.В конце концов, лямбда-функция - это просто функция.

1 голос
/ 25 октября 2010

Я могу ошибаться, и это только предположение, но вы пытались разделить ваш класс (и, по сути, ваш файл) с ключевым словом part?

1 голос
/ 24 октября 2010

VS, вероятно, задыхается от этого, потому что это такой большой, сложный отдельный оператор.

Поскольку единственными связями между OwnCost и контекстом LINQ являются ссылки на cfvc и lastValue4, мне кажется, что вымог вычислить OwnCost на отдельном шаге после начального запроса запроса LINQ.Сохраните lastValue4 в анонимном типе, созданном оператором LINQ, удалите OwnCost и удалите .ToList () с конца.Вам не нужно хранить значение cfvc, поскольку единственное, для чего вы его используете, - это доступ к .FrameVariationCategory, который вы уже захватили во 2-м поле анонимного типа.

В отдельномвыберите оператор из набора результатов CustFVC, чтобы создать OwnCost для каждого элемента, чтобы создать новый набор результатов, который содержит все искомые биты данных.Вызовите .ToList () для этого второго набора результатов.Это должно привести к эквивалентным результатам к заявлению монстра за то же время.

Если это большой набор результатов, будьте осторожны с многократным повторением данных.Если вы используете foreach для вычисления OwnCost для каждого элемента в исходном наборе результатов, вы будете проходить через данные в два раза - вдвое больше работы, чем один запрос LINQ монстра.

Если вы используете запрос LINQ длявторая операция, она не должна вызывать каких-либо дополнительных проходов по данным сверх того, что у вас уже есть - LINQ оценивается лениво, поэтому следующая строка фактически не извлекается до тех пор, пока ее не попросят.ToList () принудительно извлекает все строки.Цикл foreach заставляет извлекать все строки.Запрос LINQ, использующий запрос LINQ в качестве входных данных, не выполняет итерацию каких-либо строк входного набора результатов, он просто накапливает больше условий для оценки, когда кто-то в конечном итоге запрашивает следующую строку второго набора результатов.

1 голос
/ 24 октября 2010

Разделите это. Это одно массивное дерево выражений, с которым VS пытается разобраться. Вы можете разбить его так, чтобы некоторые из преобразований предложения SELECT происходили в LINQ-to-object. Для фонового компилятора было бы намного проще иметь дело с этим. Просто получите:

var custFVC = (from cfvc in customer.CustomerFrameVariationCategories
               let lastValue = cfvc.CustomerFrameVariationCategoryValueChanges.Where(cfvcvc => cfvcvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(cfvcvc2 => cfvcvc2.ChangeDateTime).FirstOrDefault() ?? new CustomerFrameVariationCategoryValueChange()
               let lastValue2 = cfvc.FrameVariationCategory.FrameVariation.Frame.FrameValueChanges.Where(fvc => fvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fvc2 => fvc2.ChangeDateTime).FirstOrDefault() ?? new FrameValueChange()
               let lastValue3 = cfvc.FrameVariationCategory.FrameVariationCategoryValueChanges.Where(fvcvc => fvcvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fvcvc2 => fvcvc2.ChangeDateTime).FirstOrDefault() ?? new FrameVariationCategoryValueChange()
               let lastValue4 = cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Any(fvm => (fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleValueChange()).IsActive == false)
               where lastValue.IsActive == true
               orderby cfvc.FrameVariationCategory.FrameVariation.Frame.Name, cfvc.FrameVariationCategory.Category.Name, cfvc.FrameVariationCategory.FrameVariation.Name
               select new
               { cfvc, lastValue, lastValue1, lastValue2, lastValue3}).ToList();

А затем сделайте остальные ваши манипуляции оттуда. Если результирующий набор невелик, это может быть более эффективным в любом случае, и, конечно, проще для вашей базы данных. Если набор результатов невелик, это приведет к очень небольшим затратам на производительность.

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

Имейте в виду, что простое разбиение на несколько шагов для построения одного массивного дерева выражений для IQuerable не принесет вам никакой пользы. Последняя переменная будет такой же сложной (скрытой), как и ваша, и компилятор все равно захлебнется. Суть в том, что вам нужно запустить .ToList () в начале этой манипуляции. Серия запросов LINQ-to-object к IEnumerable не будет сложной для фонового компилятора.

1 голос
/ 24 октября 2010

У меня нет никакого внутреннего понимания этой проблемы с точки зрения того, что дорого в компиляторе C #. Однако две вещи, которые выпрыгивают, когда я смотрю на ваш запрос, следующие:

  1. Количество и сложность let привязок
  2. Сложность инициализатора OwnCost члена внутри предложения select

Лучший совет, который я могу дать, - попытаться разбить запрос, чтобы разделить их на отдельные операторы, и, надеюсь, это ослабит нагрузку на компилятор.

0 голосов
/ 26 октября 2010

Ха, я сам нашел решение:)

Инстинкт Робертса о функции LINQ заставил меня погуглить.Результаты не имели отношения к данному вопросу, но небольшой код, на который я наткнулся, заставил меня задуматься о методе атаки методом перебора.Используя идею redoced о частичном классе, я наконец написал этот фрагмент кода в отдельном файле .cs:

public partial class WindowExcel
{
    private static decimal GetOwnCost(CustomerFrameVariationCategory cfvc, bool frameModuleAnyNonActive, DateTime selectedDateTime)
    {
        return cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Sum(fvm => // sum all frame variation modules
            (frameModuleAnyNonActive ? 0 : fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= selectedDateTime) // if module not active then 0
            .OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault().Porolone) + // otherwise get Porolone
            fvm.FrameModule.FrameModuleComponents.Sum(fmc => // add to Porolone sum of all module components
                (fmc.Article.ArticleDetails.Any() ? fmc.Article.ArticleDetails.Sum(ad => // if any article details then use A*L*W*T instead of Amount
                    WindowExcel.MultiplyArticleDetailValues(ad.ArticleDetailValueChanges.Where(advc => advc.ChangeDateTime <= selectedDateTime)
                    .OrderByDescending(advc2 => advc2.ChangeDateTime).FirstOrDefault() ?? new ArticleDetailValueChange())) :
                    WindowExcel.GetModuleComponentAmount(fmc.FrameModuleComponentValueChanges.Where(fmcvc => fmcvc.ChangeDateTime <= selectedDateTime) // no details = get amount
                    .OrderByDescending(fmcvc2 => fmcvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleComponentValueChange())) * // times article values
                    WindowExcel.MultiplyArticleValues(fmc.Article.ArticleValueChanges.Where(avc => avc.ChangeDateTime <= selectedDateTime)
                    .OrderByDescending(avc2 => avc2.ChangeDateTime).FirstOrDefault() ?? new ArticleValueChange())));
    }
}

И в своем гигантском запросе LINQ я переписал OwnCost следующим образом:

OwnCost = WindowExcel.GetOwnCost(cfvc, lastValue4, this.SelectedDateTime)

Редактирование метода GetOwnCost все еще мучительно медленно, как это было исключено, но по крайней мере остальная часть моего проекта теперь пригодна для использования.Я не уверен, что эта грубая сила делает производительность.Тот факт, что я не могу перефразировать CustomerFrameVariationCategory и что дерево выражений OwnCost находится внутри метода, а не в самом запросе LINQ, вызывает вопросы.Думаю, мне придется профилировать это в какой-то момент, но это еще одна проблема.

Теперь к деликатному вопросу о том, что пометить в качестве ответа.Хотя я ценю все комментарии, пока ни один из ответов не был верным (никакого конкретного решения), поэтому я должен отметить свой пост как ответ.Но я буду голосовать за повторные ответы и ответы Роберта за то, что они указали мне правильное направление.

Буду признателен, если кто-нибудь сможет прокомментировать возможные влияния производительности выполнения кода для моего решения по сравнению с исходным кодом.

PS!Запись этого в Internet Explorer 8 снова мучительно медленная из-за постоянной загрузки процессора (как-то связана с окраской кода).Так что это не только проблема VS ...

Редактировать:

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

По справедливости я отметил пост Роберта как ответ:)

0 голосов
/ 24 октября 2010

Вместо написания LINQ to SQL вы можете написать хранимую процедуру для этого, чтобы делать все эти вещи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...