EF Code First首先提高自引用的性能,一对多关系

时间:2012-07-23 19:38:06

标签: c# entity-framework recursion query-optimization

我有AccountGroup这是一个自引用实体。叶子AccountGroup可以包含1个或多个Accounts。两个实体都具有Balance属性。每个AccountGroup都有Balance,它可以是子组中Balance的总和,也可以是所有帐户Balance的总和(如果是叶组)。< / p>

为了构建所有AccountGroupAccount的树列表,我必须递归地遍历这个对象图,这会导致很多(我的意思是很多!!!)调用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个或多个AccountGroup,如果是叶子,则有1个或多个Account)

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);
        }
    }
}

1 个答案:

答案 0 :(得分:0)

CTE方法

有两种方法可以提高针对树数据类型的查询速度。第一个(也可能是最简单的)是使用存储过程和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" });

展平树形结构

第二种方法是构建树的平面表示。您可以将树展平为扁平结构以进行快速查询,然后在平面结构和实际树节点之间使用链接来剪切自引用实体。您可以使用上面的递归CTE查询构建平面结构。

这只是一种方法,但有很多关于这个问题的论文:

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

编辑:添加其他说明 只需注意,递归CTE缓存是在迭代结构之前查询的符号。这是编写查询以解决问题的最快速,最简单的方法。但是,这个HAS是一个SQL查询。您可以直接使用execute sql,也可以执行SProc。 Sprocs在运行后缓存执行图,因此它们比在运行之前必须构建执行计划的本机查询执行得更好。这完全取决于你。

树的平面表示问题是您必须定期重建或不断维护平面结构。根据您的查询路径,将确定您应该使用哪种展平算法,但最终结果保持不变。扁平结构是在EF中“完成”您想要做的事情的唯一方法,而不必通过DBConnection欺骗和执行原始SQL。