Entity Framework如何使用递归层次结构?Include()似乎无法使用它

Vic*_*ues 69 c# entity-framework visual-studio-2008

我有一个Item.Item有一个Category.

CategoryID,Name,ParentChildren.ParentChildrenCategory太.

当我为特定的LINQ to Entities查询时Item,它不返回相关的Category,除非我使用该Include("Category")方法.但它并没有带来父母和孩子的完整范畴.我能做到Include("Category.Parent"),但这个对象就像一棵树,我有一个递归的层次结构,我不知道它的结束.

如何让EF完全加载Category父项和子项,父项与父项和子项一起加载,依此类推?

这不适用于整个应用程序,出于性能考虑,仅需要此特定实体(类别).

Shi*_*iji 22

而不是使用Include您可以使用的方法Load.

然后你可以为每个人做一个并循环所有的孩子,加载他们的孩子.然后通过他们的孩子为每个人做一个,等等.

你所经历的等级数将被硬编码为你拥有的每个循环的数量.

以下是使用的示例Load:http://msdn.microsoft.com/en-us/library/bb896249.aspx

  • 出于性能原因,这是一个非常糟糕的解决方案:每个级别都是数据库的另一个行程,如果启用了延迟加载,它会变得更糟.更多信息:http://stackoverflow.com/a/22024714/237723 (27认同)
  • 这个链接毫无价值,没有示例代码 (7认同)

Ale*_*mes 14

如果你肯定想要加载整个层次结构,那么如果是我,我会尝试编写一个存储过程,它的工作就是返回层次结构中的所有项目,返回你首先要求的那个(以及随后的子项).

然后让EF的关系修复确保它们都被连接起来.

即:像:

// the GetCategoryAndHierarchyById method is an enum
Category c = ctx.GetCategoryAndHierarchyById(1).ToList().First();
Run Code Online (Sandbox Code Playgroud)

如果您已正确编写存储过程,则实现层次结构中的所有项目(即ToList())将使EF关系修复启动.

然后你想要的项目(First())应该加载它的所有子项,并且应该加载它们的子项等.所有这些都是从那个存储过程调用填充的,所以也没有MARS问题.

希望这可以帮助

亚历克斯


Bre*_*yan 6

如果您碰巧加载了所有递归实体,尤其是在类别上,可能会很危险,最终可能会比您讨价还价更多:

Category > Item > OrderLine > Item
                  OrderHeader > OrderLine > Item
         > Item > ...
Run Code Online (Sandbox Code Playgroud)

你突然加载了大部分数据库,你也可以加载发票行,然后是客户,然后加载所有其他发票.

你应该做的是如下:

var qryCategories = from q in ctx.Categories
                    where q.Status == "Open"
                    select q;

foreach (Category cat in qryCategories) {
    if (!cat.Items.IsLoaded)
        cat.Items.Load();
    // This will only load product groups "once" if need be.
    if (!cat.ProductGroupReference.IsLoaded)
        cat.ProductGroupReference.Load();
    foreach (Item item in cat.Items) {
        // product group and items are guaranteed
        // to be loaded if you use them here.
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,更好的解决方案是构建查询以使用结果构建匿名类,这样您只需要访问一次数据存储区.

var qryCategories = from q in ctx.Categories
                    where q.Status == "Open"
                    select new {
                        Category = q,
                        ProductGroup = q.ProductGroup,
                        Items = q.Items
                    };
Run Code Online (Sandbox Code Playgroud)

这样,您可以根据需要返回字典结果.

请记住,您的情境应该尽可能短暂.

  • 这会导致大量的数据库往返,而急切加载旨在避免这种情况。 (2认同)
  • 这就是为什么我说“但是,更好的解决方案是构建查询以使用结果构建匿名类,因此您只需单击数据存储一次”,正如我指出的那样,急于加载并不总是一个好的解决方案。另请注意,您的参考数据将仅加载一次,从而在上下文生存期内减少命中。 (2认同)

Shi*_*mmy 6

使用这种调用 的硬编码版本的扩展方法Include来实现包含的动态深度级别,效果很好。

namespace System.Data.Entity
{
  using Linq;
  using Linq.Expressions;
  using Text;

  public static class QueryableExtensions
  {
    public static IQueryable<TEntity> Include<TEntity>(this IQueryable<TEntity> source,
      int levelIndex, Expression<Func<TEntity, TEntity>> expression)
    {
      if (levelIndex < 0)
        throw new ArgumentOutOfRangeException(nameof(levelIndex));
      var member = (MemberExpression)expression.Body;
      var property = member.Member.Name;
      var sb = new StringBuilder();
      for (int i = 0; i < levelIndex; i++)
      {
        if (i > 0)
          sb.Append(Type.Delimiter);
        sb.Append(property);
      }
      return source.Include(sb.ToString());
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

用法:

var affiliate = await DbContext.Affiliates
  .Include(3, a => a.Referrer)
  .SingleOrDefaultAsync(a => a.Id == affiliateId);
Run Code Online (Sandbox Code Playgroud)

无论如何,同时,在 EF 回购中加入有关它的讨论


Joe*_*aus 5

您不想递归加载层次结构,除非您允许用户迭代地向下/向上钻取树:递归的每个级别都是对数据库的另一次访问。类似地,您需要延迟加载以防止在呈现到页面或通过网络服务发送时遍历层次结构时进一步的数据库旅行。

相反,翻转您的查询: GetCatalogInclude其中的项目。这将使您获得分层(导航属性)和扁平化的所有项目,因此现在您只需要排除根中存在的非根元素,这应该非常简单。

我遇到了这个问题,并在此处为另一个问题提供了此解决方案的详细示例