代码之家  ›  专栏  ›  技术社区  ›  zam6ak

EF Code First提高了自引用、一对多关系的性能

  •  5
  • zam6ak  · 技术社区  · 12 年前

    我有一个 AccountGroup 其是自参考实体。一片树叶 会计组 可以包含1个或多个 Accounts .这两个实体都有 Balance 所有物每个 会计组 有一个 均衡 其为 均衡 s的子群或总和 均衡 所有帐户的s(如果是叶组)。

    为了构建一个所有 会计组 s和 Account s我必须递归地遍历这个对象图,这会导致对DB的大量调用(我的意思是很多!!)。。。

    有什么方法可以改进这一点,从而减少数据库调用的数量吗?

    谢谢

    这是精简后的代码

    账户(仅属于1个账户组)

    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个或多个子账户)

    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 回复  |  直到 12 年前
        1
  •  0
  •   VulgarBinary    12 年前

    CTE方法

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

    编辑

    使用以下命令内联执行存储过程或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缓存是在遍历结构之前查询的符号。这是编写查询以解决问题的最快、最简单的方法。但是,这必须是一个SQL查询。您可以直接使用execute sql,也可以执行SProc。Sprocs在运行后缓存执行图,因此它们的性能优于必须在运行前构建执行计划的本机查询。这完全取决于你。

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