entity framework如何与recursion层次结构一起工作? 包括()似乎不能使用它

我有一个ItemItem有一个Category

CategoryIDNameParentChildrenParentChildren也属于Category

当我做一个特定Item的LINQ to Entities查询时,它不会返回相关的Category ,除非我使用Include("Category")方法。 但是它并没有把它的全部范畴与它的父母和孩子带到一起。 我可以做Include("Category.Parent") ,但是这个对象就像一棵树,我有一个recursion的层次结构,我不知道它在哪里结束。

我怎样才能使EF完全加载Category ,与父母和子女,父母与父母和子女,等等?

这不是整个应用程序的事情,考虑到性能方面的考虑,只需要这个特定的实体,类别。

您可以使用Load来代替使用Include方法。

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

你下去的层数将被硬编码为每个循环的数量。

这是一个使用Load的例子: http : //msdn.microsoft.com/en-us/library/bb896249.aspx

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

然后让EF的关系修复确保他们都联系起来。

即像这样的东西:

 // the GetCategoryAndHierarchyById method is an enum Category c = ctx.GetCategoryAndHierarchyById(1).ToList().First(); 

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

然后,你想要的项目(第一())应该有所有的孩子加载,他们应该有他们的孩子加载等所有填充从一个存储过程调用,所以没有MARS问题。

希望这可以帮助

亚历克斯

如果你碰巧加载了所有的recursion实体,特别是在类别上,这可能是危险的,你可能会以超过你讨价还价的方式结束:

 Category > Item > OrderLine > Item OrderHeader > OrderLine > Item > Item > ... 

突然之间,您已经加载了大部分数据库,您也可以加载发票行,然后是客户,然后是所有其他发票。

你应该做的是如下所示:

 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. } } 

然而,更好的解决scheme是构build查询来构build一个匿名类,结果只需要敲击一次数据存储。

 var qryCategories = from q in ctx.Categories where q.Status == "Open" select new { Category = q, ProductGroup = q.ProductGroup, Items = q.Items }; 

这样你可以返回一个字典结果,如果需要的话。

记住,你的上下文应尽可能短。

您应该引入一个映射表,将每个类别映射到父母和孩子,而不是将父级和子级属性添加到货物本身。

根据您需要的信息频率,可以根据需要查询。 通过在数据库中的独特约束,你可以避免无限的关系beeing成为可能。

除非允许用户迭代地向上/向下钻取树,否则您不想执行层次结构的recursion加载:recursion的每个级别都是到数据库的另一次访问。 同样的,当你渲染页面或通过web服务发送时,你需要延迟加载,以防止进一步的数据库跳转。

相反,翻转您的查询:获取Catalog ,并在其中Include项目。 这将使所有的项目都分层(导航属性)和展平,所以现在你只需要排除根目录下的非根元素,这应该是非常平凡的。

我遇到了这个问题,并在这里提供了另一个解决scheme的详细示例

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

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

用法:

 var affiliate = await DbContext.Affiliates .Include(3, a => a.Referrer) .SingleOrDefaultAsync(a => a.Id == affiliateId); 

无论如何,同时在EF回购上join讨论 。

这里是一个聪明的recursion函数,我在这里find了这个工作:

 public partial class Category { public IEnumerable<Category> AllSubcategories() { yield return this; foreach (var directSubcategory in Subcategories) foreach (var subcategory in directSubcategory.AllSubcategories()) { yield return subcategory; } } } 

您也可以在数据库中创build一个tablevalued函数,并将其添加到您的DBContext。 那么你可以从你的代码中调用它。

这个例子要求你从nuget导入EntityFramework.Functions。

 public class FunctionReturnType { public Guid Id { get; set; } public Guid AnchorId { get; set; } //the zeroPoint for the recursion // Add other fields as you want (add them to your tablevalued function also). // I noticed that nextParentId and depth are useful } public class _YourDatabaseContextName_ : DbContext { [TableValuedFunction("RecursiveQueryFunction", "_YourDatabaseContextName_")] public IQueryable<FunctionReturnType> RecursiveQueryFunction( [Parameter(DbType = "boolean")] bool param1 = true ) { //Example how to add parameters to your function //TODO: Ask how to make recursive queries with SQL var param1 = new ObjectParameter("param1", param1); return this.ObjectContext().CreateQuery<FunctionReturnType>( $"RecursiveQueryFunction(@{nameof(param1)})", param1); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //add both (Function returntype and the actual function) to your modelbuilder. modelBuilder.ComplexType<FunctionReturnType>(); modelBuilder.AddFunctions(typeof(_YourDatabaseContextName_), false); base.OnModelCreating(modelBuilder); } public IEnumerable<Category> GetParents(Guid id) { //this = dbContext return from hierarchyRow in this.RecursiveQueryFunction(true) join yourClass from this.Set<YourClassThatHasHierarchy>() on hierarchyRow.Id equals yourClass.Id where hierarchyRow.AnchorId == id select yourClass; } } 

尝试这个

 List<SiteActionMap> list = this.GetQuery<SiteActionMap>() .Where(m => m.Parent == null && m.Active == true) .Include(m => m.Action) .Include(m => m.Parent).ToList(); if (list == null) return null; this.GetQuery<SiteActionMap>() .OrderBy(m => m.SortOrder) .Where(m => m.Active == true) .Include(m => m.Action) .Include(m => m.Parent) .ToList(); return list; 

@议会给了我一个EF6的想法。 带有方法的类别的示例将所有父项加载到根节点和所有子项。

注:仅用于非性能严重的操作。 具有来自http://nosalan.blogspot.se/2012/09/hierarchical-data-and-entity-framework-4.html的; 1000个节点性能的示例。

 Loading 1000 cat. with navigation properties took 15259 ms Loading 1000 cat. with stored procedure took 169 ms 

码:

 public class Category { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public string Name { get; set; } public int? ParentId { get; set; } public virtual Category Parent { get; set; } public virtual ICollection<Category> Children { get; set; } private IList<Category> allParentsList = new List<Category>(); public IEnumerable<Category> AllParents() { var parent = Parent; while (!(parent is null)) { allParentsList.Add(parent); parent = parent.Parent; } return allParentsList; } public IEnumerable<Category> AllChildren() { yield return this; foreach (var child in Children) foreach (var granChild in child.AllChildren()) { yield return granChild; } } }