5

私はAccountGroup自己参照エンティティである を持っています。リーフAccountGroupには 1 つ以上の を含めることができますAccounts。どちらのエンティティにもBalanceプロパティがあります。それぞれAccountGroupに、サブグループの の合計、またはBalanceすべてのアカウントの の合計 (リーフ グループの場合) があります。BalanceBalance

AccountGroupすべての とのツリー リストを作成するAccountには、このオブジェクト グラフを再帰的にトラバースする必要があります。これにより、DB への呼び出しが大量に発生します。

DB 呼び出しの数を減らすようにこれを改善する方法はありますか?

ありがとう

ここにトリミングされたコードがあります

アカウント (1 つの AccountGroup のみに属します)

public class Account
{
    public int Id { get; set; }
    public int GroupId { get; set; }
    public string Name { get; set; }
    public decimal Balance { get; set; }
    public string AccountType { get; set; }

    public virtual AccountGroup Group { get; set; }
}

AccountGroup (0 個または多数の AccountGroups を持ち、リーフの場合は 1 個以上の Accounts を持ちます)

public class AccountGroup
{
    public AccountGroup()
    {
        Accounts = new HashSet<Account>();
        Groups = new HashSet<AccountGroup>();
    }

    public int Id { get; set; }
    public bool IsRoot { get { return Parent == null; } }
    public bool IsLeaf { get { return !Groups.Any(); } }
    public decimal Balance { get { return IsLeaf ? Accounts.Sum(a => a.Balance) : Groups.Sum(g => g.Balance); } } // if leaf group, get sum of all account balances, otherwise get sum of all subgroups
    public int? ParentId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public virtual ISet<Account> Accounts { get; private set; }
    public virtual ISet<AccountGroup> Groups { get; private set; }
    public virtual AccountGroup Parent { get; set; }
}

呼び出しコード

// start processing root groups (ones without parent)
foreach (var rootGroup in db.AccountGroups.Include(g=>g.Groups).Where(g => g.ParentId == null))
{
    TraverseAccountGroup(rootGroup, 0);
}

// recursive method
private static void TraverseAccountGroup(AccountGroup accountGroup, int level)
{
    //
    // process account group
    //
    Console.WriteLine("{0}{1} ({2})", String.Empty.PadRight(level * 2, '.'), accountGroup.Name, level);
    //
    // if subgroups exist, process recursivelly
    //
    if (accountGroup.Groups.Any())
    {
        foreach (var subGroup in accountGroup.Groups)
        {
            TraverseAccountGroup(subGroup, level + 1);
        }
    }
    //
    // otherwise, process accounts belonging to leaf subgroup
    //
    else
    {
        foreach (var account in accountGroup.Accounts)
        {
            Console.WriteLine("ACCOUNT [{0}]", account.Name);
        }
    }
}
4

1 に答える 1

0

CTEアプローチ

ツリー データ型に対するクエリの速度を上げる方法は 2 つあります。最初の (おそらく最も簡単な) 方法は、ストアド プロシージャと EF の SQL 実行機能を使用してツリーを読み込むことです。SProc がキャッシュされ、結果セットの実行速度が向上します。sproc のクエリに対する私の推奨事項は、再帰的な CTE です。

http://msdn.microsoft.com/en-us/library/ms186243(v=sql.105).aspx

with <CTEName> as
(
     SELECT
         <Root Query>
     FROM <TABLE>

     UNION ALL

     SELECT
         <Child Query>
     FROM <TABLE>
     INNER JOIN <CTEName>
         ON <CTEJoinCondition>
     WHERE 
          <TERMINATION CONDITION>

)

編集

次のようにインラインで sproc または CTE を実行します。

DbContext ctx = new SampleContext();
ctx.Database.SqlQuery<YourEntityType>(@"SQL OR SPROC COMMAND HERE", new[] { "Param1", "Param2", "Etc" });

ツリー構造を平坦化する

2 番目のアプローチは、ツリーのフラットな表現を構築することです。ツリーをフラット構造にフラット化してクエリをすばやく実行し、フラット構造と実際のツリー ノード間のリンケージを使用して、自己参照エンティティを切り取ることができます。上記の再帰 CTE クエリを使用して、フラットな構造を構築できます。

これは 1 つのアプローチにすぎませんが、この件に関する多くの論文があります。

http://www.governor.co.uk/news-plus-views/2010/5/17/depth-first-tree-flattening-with-the-yield-keyword-in-c-sharp/

編集: 追加の説明を追加 する 注意点として、再帰 CTE キャッシュは、構造を反復処理する前のクエリのシンボルです。これは、問題を解決するためのクエリを作成する最も速くて簡単な方法です。ただし、これは SQL クエリでなければなりません。execute sql を直接使用することも、SProc を実行することもできます。Sprocs は、実行後に実行グラフをキャッシュするため、実行前に実行計画を作成する必要があるネイティブ クエリよりも優れたパフォーマンスを発揮します。これは完全にあなた次第です。

ツリーのフラットな表現の問題は、フラットな構造を定期的に再構築または維持する必要があることです。クエリ パスに応じて、使用する平坦化アルゴリズムが決まりますが、最終結果は変わりません。フラット構造は、DBConnection を介して生の SQL をごまかして実行することなく、EF 内でやりたいことを「達成」する唯一の方法です。

于 2012-07-23T21:43:54.637 に答える