0

我们有一个简单的计算引擎,它接收存储在数据库中的字符串(例如“(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 的关联表,其中包含有关会计项目值的位置信息。

非常感谢你。

4

3 回答 3

1

我会首先分解方法并在更细粒度的级别进行分析;找出导致性能下降的确切原因。

可能问题不在于 Linq,而是在数据库中 - 您是否完全分析和优化了您的数据库?您是否有合理的索引,它们是否被 EF 生成的 sql 使用?

我看不出您的 linq 查询有任何明显错误。

于 2011-02-16T20:48:42.367 回答
0

永远不要低估循环内查询的力量。也许您最好的选择是从不同的方法来看待它,然后将其中的一些循环查询拉出来。你有没有运行任何计时器来查看它在哪里花费的时间最长?我愿意打赌这是 foreach 循环中的那些 LINQ 查询。

于 2011-02-16T20:38:51.867 回答
0

Linq JOIN操作将遍历整个数据库,然后在ON语句上“合并”结果。
然后它将遍历结果并按WHERE语句条件进行过滤。

因此,如果 :

context.AccountingItemValues = N 
context.STARS_DistrictInputData = M

然后连接操作会为您提供一个大小为 Max(M,N)(最坏情况)的表(让我们像 SQL 一样思考一下)。

然后它将遍历整个结果表,并使用WHERE语句过滤结果。

所以你在整个数据库中循环了两次以上。并且 JOIN 操作不是线性的,因此您可以在整个过程中获得更多的迭代。

改善

在联接之前使用表特定的where条件,因此您可以在联接之前减小表的大小。
那会给你

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

然后在更小的表上完成连接操作。

于 2011-02-16T21:11:31.437 回答