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

Linq JOIN操作はデータベース全体をループし、結果をONステートメントに「マージ」します。
次に、結果をループし、WHEREステートメントの条件でフィルタリングします。

したがって、次の場合:

context.AccountingItemValues = N 
context.STARS_DistrictInputData = M

次に、結合操作により、サイズMax(M、N)(最悪の場合)の結果(SQLのように考えてみましょう)テーブルが得られます。

次に、結果テーブル全体に対して実行され、WHEREステートメントで結果をフィルタリングします。

したがって、データベース全体を2回以上ループしていることになります。また、JOIN操作は線形ではないため、全体でより多くの反復を取得できます。

改善する

結合前のテーブル固有のwhere条件を使用して、結合前のテーブルのサイズを縮小します。
それはあなたに与えるでしょう

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

次に、結合操作がはるかに小さいテーブルで実行されます。

于 2011-02-16T21:11:31.437 に答える
0

ループ内でのクエリの威力を決して過小評価しないでください。おそらく、あなたの最善の策は、別のアプローチからそれを見て、それらのループしたクエリのいくつかを引き出すことでしょう. タイマーを実行して、正確に最も時間がかかっている場所を確認しましたか? foreach ループ内の LINQ クエリであると断言できます。

于 2011-02-16T20:38:51.867 に答える