如何在Entity Framework代码优先方法中映射自我的递归关系

我想要创建的只是基本的递归类别。 如果RootCategory_Id设置为null,则类别为root,如果将其设置为某个id,则属于某个其他类别。 我在Seed()方法中添加了两个子类别的类别进行测试,但它不起作用。 (之后我检查了数据库,插入了)

分类模型

 public class Category { public int ID { get; set; } public Category RootCategory { get; set; } // This one works good, it also creates "RootCategory_Id" in database on "update-database" public ICollection ChildCategories { get; set; } // This is always null, how to map it correctly? public string Name { get; set; } } 

种子方法

 protected override void Seed(Test.Infrastructure.TestDataContext context) { context.Categories.Add(new Category() { Name = "First Category", ChildCategories = new List() { new Category(){ Name = "Second Category" }, new Category(){ Name = "Third Category" } } }); context.SaveChanges(); } 

这就是我测试它不起作用的方式

 public ActionResult Test() { // After checking DB my root is 4, and two categories that have RootCategory_Id set to 4 var c = _db.Categories.Where(x => x.ID == 4).Single(); return Content(c.ChildCategories.FirstOrDefault().Name); // Always returns null, even c.ChildCategories.Count() returns 'null' } 

我想要实现的图片

这是使用linq-to-sql从数据库优先方法生成的
在此处输入图像描述

我真的不想在这里使用necro-thread,但我一直在寻找解决这个问题的方法。

这是我的解决方案; 它有点长,但它允许Code First编程的更具可扩展性的方法。 它还引入了策略模式以允许SoC尽可能保持为POCO。

第1步:创建实体原语和接口。

IEntity界面:

 ///  /// Represents an entity used with Entity Framework Code First. ///  public interface IEntity { ///  /// Gets or sets the identifier. ///  ///  /// The identifier. ///  int Id { get; set; } } 

IRecursiveEntity接口:

 ///  /// Represents a recursively hierarchical Entity. ///  ///  public interface IRecursiveEntity  where TEntity : IEntity { ///  /// Gets or sets the parent item. ///  ///  /// The parent item. ///  TEntity Parent { get; set; } ///  /// Gets or sets the child items. ///  ///  /// The child items. ///  ICollection Children { get; set; } } 

实体抽象类:

 ///  /// Acts as a base class for all entities used with Entity Framework Code First. ///  public abstract class Entity : IEntity { ///  /// Gets or sets the identifier. ///  ///  /// The identifier. ///  public int Id { get; set; } } 

RecursiveEntity抽象类:

 ///  /// Acts as a base class for all recursively hierarchical entities. ///  /// The type of the entity. public abstract class RecursiveEntity : Entity, IRecursiveEntity where TEntity : RecursiveEntity { #region Implementation of IRecursive ///  /// Gets or sets the parent item. ///  ///  /// The parent item. ///  public virtual TEntity Parent { get; set; } ///  /// Gets or sets the child items. ///  ///  /// The child items. ///  public virtual ICollection Children { get; set; } #endregion } 

注意:有些人建议对此post进行有关此课程的编辑。 该类只需要接受RecursiveEntity作为约束,而不是IEntity因此它被约束为仅处理递归实体。 这有助于缓解类型不匹配exception。 如果您使用IEntity ,则需要添加一些exception处理来对抗不匹配的基类型。

使用IEntity将提供完全有效的代码,但在任何情况下它都无法正常工作。 使用最可用的根,并不总是最佳实践,在这种情况下,我们需要进一步限制inheritance树而不是在根级别。 我希望这是有道理的。 这是我最初玩的东西,但填充数据库时遇到了很大的问题; 特别是在entity framework迁移期间,您没有精细的调试控件。

在测试期间,它似乎也不适合IRecursiveEntity 。 我可能会很快回到这里,因为我正在刷新一个使用它的旧项目,但它在这里写的方式是完全有效和有效的,我记得调整它直到它按预期工作。 我认为在代码优雅和inheritanceheirarchy之间存在折衷,其中使用更高级别的类意味着在IEntityIRecursiveEntity之间投射了很多属性,这反过来又提供了较低的性能并且看起来很丑陋。

第2步:派生RecursiveEntity。

我用过原来问题的例子……

类别混凝土类:

 public class Category : RecursiveEntity { ///  /// Gets or sets the name of the category. ///  ///  /// The name of the category. ///  public string Name { get; set; } } 

除了非衍生属性之外,我已经从课堂上删除了所有内容。 Category从其与RecursiveEntity类的自关联genericsinheritance中派生其所有其他属性。

第3步:扩展方法(可选)。

为了使整个事情更容易管理,我添加了一些扩展方法,以便轻松地将新子项添加到任何父项。 我发现,困难在于你需要设置一对多关系的两端,只是将子项添加到列表中就不允许你按照预期的方式处理它们。 这是一个简单的修复,从长远来看可以节省大量的时间。

RecursiveEntityEx静态类:

 ///  /// Adds functionality to all entities derived from the RecursiveEntity base class. ///  public static class RecursiveEntityEx { ///  /// Adds a new child Entity to a parent Entity. ///  /// The type of recursive entity to associate with. /// The parent. /// The child. /// The parent Entity. public static TEntity AddChild(this TEntity parent, TEntity child) where TEntity : RecursiveEntity { child.Parent = parent; if (parent.Children == null) parent.Children = new HashSet(); parent.Children.Add(child); return parent; } ///  /// Adds child Entities to a parent Entity. ///  /// The type of recursive entity to associate with. /// The parent. /// The children. /// The parent Entity. public static TEntity AddChildren(this TEntity parent, IEnumerable children) where TEntity : RecursiveEntity { children.ToList().ForEach(c => parent.AddChild(c)); return parent; } } 

一旦你掌握了所有这些,你就可以如此种下它:

种子方法

 ///  /// Seeds the specified context. ///  /// The context. protected override void Seed(Test.Infrastructure.TestDataContext context) { // Generate the root element. var root = new Category { Name = "First Category" }; // Add a set of children to the root element. root.AddChildren(new HashSet { new Category { Name = "Second Category" }, new Category { Name = "Third Category" } }); // Add a single child to the root element. root.AddChild(new Category { Name = "Fourth Category" }); // Add the root element to the context. Child elements will be saved as well. context.Categories.AddOrUpdate(cat => cat.Name, root); // Run the generic seeding method. base.Seed(context); } 

懒惰加载打开或关闭?,

您可能需要在查询中包含子关系,如此

 _db.Categories.Include("ChildCategories").FirstOrDefault(x => x.ID == 4)