Как оптимизировать запросы Linq и обновить значения для скорости - PullRequest
0 голосов
/ 16 февраля 2011

У нас есть простой механизм вычислений, который принимает строку (например, «(x + y) * 4»), которая хранится в базе данных, извлекает значения для x и y из базы данных, выполняет вычисление и сохраняетрезультат в базу данных.Кажется, что это занимает слишком много времени, и я боюсь, что попал в ловушку Linq.Пожалуйста, дайте мне знать, если есть способы улучшить это:

 public Nullable<decimal> CalculateFormulaByYearDistrict(int formulaId, int fundingYearId, int districtId)
        {
            string formulaText = "";
            decimal? retValue = null;    

            using (STARSEntities context = new STARSEntities())
            {

                var formulaItems = from fi in context.STARS_FormulaItem
                                   where fi.FormulaId == formulaId
                                   select fi;

                STARS_Formula formula = formulaItems.FirstOrDefault().STARS_Formula;
                formulaText = formula.FormulaText;

                foreach (STARS_FormulaItem formulaItem in formulaItems)
                {
                    int accountingItemId = formulaItem.AccountingItemId;

                    var itemValue = (from iv in context.AccountingItemValues
                                     join di in context.STARS_DistrictInputData
                                     on iv.DomainSpecificId equals di.DistrictInputDataId
                                     where (di.DistrictId == districtId || di.DistrictId == -1) //District -1 is the invalid and universal district for coefficients
                                     && di.DomainYearReportingPeriod.FundingYearId == fundingYearId
                                     && iv.AccountingItemId == accountingItemId
                                     select iv).SingleOrDefault();
                    //If no value exists for the requested Assessment Item Value, then force an error message into the formula text
                    //to be thrown during calculate.
                    if (itemValue != null)
                        formulaText = Regex.Replace(formulaText, @"\b" + formulaItem.ItemCode + @"\b", itemValue.Amount.ToString());
                    else
                        formulaText = Regex.Replace(formulaText, @"\b" + formulaItem.ItemCode + @"\b", "No Value Exists for " + formulaItem.ItemCode);
                }
                switch (formula.FormulaTypeId)
                {
                    case (int)FormulaType.CALC:
                        retValue = Calculate(formulaText);
                        break;
                    case (int)FormulaType.EXPONENT:
                        // pull the number directly after e and and calculate the Math.Exp(value) and then push that into the calculation.
                        retValue = Calculate(ReplaceExponent(formulaText));
                        break;
                    case (int)FormulaType.IFTHEN:
                        // evaluate the If statements and pass any math to the calculator.
                        retValue = Calculate(EvaluateIf(formulaText));
                        break;
                    default:
                        break;
                }
            }            
            return retValue;
        }

public bool CalculateAndSaveResults(DistrictDataCategory category, List<int> districtIds, int fundingYearId, int userId)
        {
            //Optimization Logic
            DateTime startTime = DateTime.Now;
            Debug.WriteLine("Starting Calculate and Save at:" + startTime.ToString());

            using (STARSEntities context = new STARSEntities())
            {

                var formulas = from f in context.STARS_FormulaCategory
                               where f.DistrictDataCategoryId == (int)category
                               select f.STARS_Formula;

                foreach (var districtId in districtIds)
                {
                    Debug.WriteLine("District: " + districtId.ToString());
                    DateTime districtStartTime = DateTime.Now;

                    foreach (var formula in formulas)
                    {
                        var itemValue = (from iv in context.AccountingItemValues
                                         join di in context.STARS_DistrictInputData
                                         on iv.DomainSpecificId equals di.DistrictInputDataId
                                         where (di.DistrictId == districtId)
                                         && di.DomainYearReportingPeriod.FundingYearId == fundingYearId
                                         && iv.AccountingItemId == formula.ResultAccountingItemId
                                         select new { iv, di }).SingleOrDefault();

                        itemValue.iv.Amount = CalculateFormulaByYearDistrict(formula.FormulaId, fundingYearId, districtId);

                        //Update Actual Amount Record
                        itemValue.iv.LastUpdated = DateTime.Now;
                        itemValue.iv.UpdatedBy = userId;

                        //Update District Data Import Record
                        itemValue.di.LastUpdated = DateTime.Now;
                        itemValue.di.UpdatedBy = userId;
                    }
                    Debug.WriteLine("District Calculation took: " + ((TimeSpan)(DateTime.Now - districtStartTime)).ToString() + "for " + districtId.ToString());
                }

                context.SaveChanges();
            }
            Debug.WriteLine("Finished Calculate and Save at:" + ((TimeSpan)(DateTime.Now - startTime)).ToString());
            return true;
        }

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

Большое спасибо.

Ответы [ 3 ]

1 голос
/ 16 февраля 2011

Я бы начал с разбивки методов и профилирования на более детальном уровне;точно определить, что является причиной снижения производительности.

Возможно, проблема не в Linq, а в базе данных - вы вообще профилировали и оптимизировали свою базу данных?Есть ли у вас разумные индексы и используются ли они в sql, который генерирует EF?

Я не вижу ничего плохого в ваших запросах linq.

0 голосов
/ 17 февраля 2011

Операция Linq JOIN зацикливает всю базу данных, а затем "объединяет" результаты в операторе ON .
Затем он зацикливается на результате и фильтруется по условиям оператора WHERE .

Так что если:

context.AccountingItemValues = N 
context.STARS_DistrictInputData = M

Тогда операция join дает вам результат (давайте подумаем, как SQL) таблицу размера Max (M, N) (наихудший случай).

Затем он пройдет по всей таблице результатов и отфильтрует результаты с помощью оператора WHERE .

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

Улучшение :

Используйте для таблицы условия перед объединением, чтобы уменьшить размер таблиц до объединения.
Это даст вам

context.AccountingItemValues = numberOf(accountingItemId)
context.STARS_DistrictInputData = numberOf(fundingYearId)

Затем операция объединения выполняется на гораздо меньших таблицах.

0 голосов
/ 16 февраля 2011

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

...