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

时间:2022-01-20 02:14:19

All I want to create is basic recursive category. Category is root if RootCategory_Id is set to null and it belongs to some other category if it is set to some id. I've added category with two child-categories in Seed() method to test and it does not work. (I've checked DB afterwards, there are inserted)

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

Category model

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<Category> ChildCategories { get; set; } // This is always null, how to map it correctly?

    public string Name { get; set; }
}

Seed method

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

    context.SaveChanges();
}

This is how I tested that it does not work

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

Picture of what I want to achieve

this was generated from database-first approach using linq-to-sql
如何在Entity Framework代码优先方法中映射自我的递归关系

这是使用linq-to-sql从数据库优先方法生成的

2 个解决方案

#1


10  

I didn't really want to necro-thread here, but I've been searching for methods on how to solve this exact problem.

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

Here's my solution; it's a bit long winded, but it allows a much more scalable approach to Code First programming. It also introduces the Strategy pattern to allow for SoC while remaining as POCO as possible.

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

Step 1: Create the entity primitives and interfaces.

IEntity Interface:

IEntity界面:

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

IRecursiveEntity Interface:

IRecursiveEntity接口:

/// <summary>
///     Represents a recursively hierarchical Entity.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IRecursiveEntity <TEntity> where TEntity : IEntity
{
    /// <summary>
    ///     Gets or sets the parent item.
    /// </summary>
    /// <value>
    ///     The parent item.
    /// </value>
    TEntity Parent { get; set; }

    /// <summary>
    ///     Gets or sets the child items.
    /// </summary>
    /// <value>
    ///     The child items.
    /// </value>
    ICollection<TEntity> Children { get; set; }
}

Entity Abstract Class:

实体抽象类:

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

RecursiveEntity Abstract Class:

RecursiveEntity抽象类:

/// <summary>
///     Acts as a base class for all recursively hierarchical entities.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public abstract class RecursiveEntity<TEntity> : Entity, IRecursiveEntity<TEntity> 
    where TEntity : RecursiveEntity<TEntity>
{
    #region Implementation of IRecursive<TEntity>

    /// <summary>
    ///     Gets or sets the parent item.
    /// </summary>
    /// <value>
    ///     The parent item.
    /// </value>
    public virtual TEntity Parent { get; set; }

    /// <summary>
    ///     Gets or sets the child items.
    /// </summary>
    /// <value>
    ///     The child items.
    /// </value>
    public virtual ICollection<TEntity> Children { get; set; }

    #endregion
}

Note: Some people have suggested edits to this post regarding this class. The class needs to only accept RecursiveEntity<TEntity> as a constraint, rather than IEntity so that it is constrained to only handle recursive entities. This helps alleviate type mismatch exceptions. If you use IEntity instead, you will need to add some exception handling to counter mismatched base types.

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

Using IEntity will give perfectly valid code, but it will not work as expected under all circumstances. Using the topmost root available, isn't always the best practice, and in this case, we need to constrain further down the inheritence tree than at that root level. I hope that makes sense. It is something I played around with at first, but I had huge problems when populating the database; especially during Entity Framework Migrations, where you have no fine grained debugging control.

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

During testing, it also didn't seem to play nice with IRecursiveEntity<TEntity> either. I may return to this soon, as I'm refreshing an old project that uses it, but the way it is written here is fully valid and working, and I remember tweaking it until it worked as expected. I think there was a trade off between code elegance and inheritance heirarchy, where using a higher level class meant casting a lot of properties between IEntity and IRecursiveEntity<IEntity>, which in turn, gave a lower performance and looked ugly.

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

Step 2: Derive the RecursiveEntity.

I've used the example from the original question...

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

Category Concrete Class:

类别混凝土类:

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

I've stripped out everything from the class apart from non-derived properties. The Category derives all its other properties from its self-associated generic inheritance of the RecursiveEntity class.

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

Step 3: Extension Methods (Optional).

To make the whole thing more managable, I've added some extension methods to easily add new children to any parent item. The difficulty, I found, was that you need to set both ends of the one-to-many relationship and merely adding the child to the list didn't allow you to handle them in the way they are intended. It's a simple fix that saves a huge amount of time in the long run.

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

RecursiveEntityEx Static Class:

RecursiveEntityEx静态类:

/// <summary>
///     Adds functionality to all entities derived from the RecursiveEntity base class.
/// </summary>
public static class RecursiveEntityEx
{
    /// <summary>
    ///     Adds a new child Entity to a parent Entity.
    /// </summary>
    /// <typeparam name="TEntity">The type of recursive entity to associate with.</typeparam>
    /// <param name="parent">The parent.</param>
    /// <param name="child">The child.</param>
    /// <returns>The parent Entity.</returns>
    public static TEntity AddChild<TEntity>(this TEntity parent, TEntity child)
        where TEntity : RecursiveEntity<TEntity>
    {
        child.Parent = parent;
        if (parent.Children == null)
            parent.Children = new HashSet<TEntity>();
        parent.Children.Add(child);
        return parent;
    }

    /// <summary>
    ///     Adds child Entities to a parent Entity.
    /// </summary>
    /// <typeparam name="TEntity">The type of recursive entity to associate with.</typeparam>
    /// <param name="parent">The parent.</param>
    /// <param name="children">The children.</param>
    /// <returns>The parent Entity.</returns>
    public static TEntity AddChildren<TEntity>(this TEntity parent, IEnumerable<TEntity> children)
        where TEntity : RecursiveEntity<TEntity>
    {
        children.ToList().ForEach(c => parent.AddChild(c));
        return parent;
    }
}

Once you have all of that in place, you can seed it thusly:

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

Seed Method

种子方法

/// <summary>
///     Seeds the specified context.
/// </summary>
/// <param name="context">The context.</param>
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<Category>
    {
        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);
}

#2


0  

is lazy loading on or off?,

懒惰加载打开或关闭?,

You may need to include the child relationship in the query like so

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

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

#1


10  

I didn't really want to necro-thread here, but I've been searching for methods on how to solve this exact problem.

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

Here's my solution; it's a bit long winded, but it allows a much more scalable approach to Code First programming. It also introduces the Strategy pattern to allow for SoC while remaining as POCO as possible.

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

Step 1: Create the entity primitives and interfaces.

IEntity Interface:

IEntity界面:

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

IRecursiveEntity Interface:

IRecursiveEntity接口:

/// <summary>
///     Represents a recursively hierarchical Entity.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IRecursiveEntity <TEntity> where TEntity : IEntity
{
    /// <summary>
    ///     Gets or sets the parent item.
    /// </summary>
    /// <value>
    ///     The parent item.
    /// </value>
    TEntity Parent { get; set; }

    /// <summary>
    ///     Gets or sets the child items.
    /// </summary>
    /// <value>
    ///     The child items.
    /// </value>
    ICollection<TEntity> Children { get; set; }
}

Entity Abstract Class:

实体抽象类:

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

RecursiveEntity Abstract Class:

RecursiveEntity抽象类:

/// <summary>
///     Acts as a base class for all recursively hierarchical entities.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public abstract class RecursiveEntity<TEntity> : Entity, IRecursiveEntity<TEntity> 
    where TEntity : RecursiveEntity<TEntity>
{
    #region Implementation of IRecursive<TEntity>

    /// <summary>
    ///     Gets or sets the parent item.
    /// </summary>
    /// <value>
    ///     The parent item.
    /// </value>
    public virtual TEntity Parent { get; set; }

    /// <summary>
    ///     Gets or sets the child items.
    /// </summary>
    /// <value>
    ///     The child items.
    /// </value>
    public virtual ICollection<TEntity> Children { get; set; }

    #endregion
}

Note: Some people have suggested edits to this post regarding this class. The class needs to only accept RecursiveEntity<TEntity> as a constraint, rather than IEntity so that it is constrained to only handle recursive entities. This helps alleviate type mismatch exceptions. If you use IEntity instead, you will need to add some exception handling to counter mismatched base types.

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

Using IEntity will give perfectly valid code, but it will not work as expected under all circumstances. Using the topmost root available, isn't always the best practice, and in this case, we need to constrain further down the inheritence tree than at that root level. I hope that makes sense. It is something I played around with at first, but I had huge problems when populating the database; especially during Entity Framework Migrations, where you have no fine grained debugging control.

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

During testing, it also didn't seem to play nice with IRecursiveEntity<TEntity> either. I may return to this soon, as I'm refreshing an old project that uses it, but the way it is written here is fully valid and working, and I remember tweaking it until it worked as expected. I think there was a trade off between code elegance and inheritance heirarchy, where using a higher level class meant casting a lot of properties between IEntity and IRecursiveEntity<IEntity>, which in turn, gave a lower performance and looked ugly.

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

Step 2: Derive the RecursiveEntity.

I've used the example from the original question...

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

Category Concrete Class:

类别混凝土类:

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

I've stripped out everything from the class apart from non-derived properties. The Category derives all its other properties from its self-associated generic inheritance of the RecursiveEntity class.

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

Step 3: Extension Methods (Optional).

To make the whole thing more managable, I've added some extension methods to easily add new children to any parent item. The difficulty, I found, was that you need to set both ends of the one-to-many relationship and merely adding the child to the list didn't allow you to handle them in the way they are intended. It's a simple fix that saves a huge amount of time in the long run.

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

RecursiveEntityEx Static Class:

RecursiveEntityEx静态类:

/// <summary>
///     Adds functionality to all entities derived from the RecursiveEntity base class.
/// </summary>
public static class RecursiveEntityEx
{
    /// <summary>
    ///     Adds a new child Entity to a parent Entity.
    /// </summary>
    /// <typeparam name="TEntity">The type of recursive entity to associate with.</typeparam>
    /// <param name="parent">The parent.</param>
    /// <param name="child">The child.</param>
    /// <returns>The parent Entity.</returns>
    public static TEntity AddChild<TEntity>(this TEntity parent, TEntity child)
        where TEntity : RecursiveEntity<TEntity>
    {
        child.Parent = parent;
        if (parent.Children == null)
            parent.Children = new HashSet<TEntity>();
        parent.Children.Add(child);
        return parent;
    }

    /// <summary>
    ///     Adds child Entities to a parent Entity.
    /// </summary>
    /// <typeparam name="TEntity">The type of recursive entity to associate with.</typeparam>
    /// <param name="parent">The parent.</param>
    /// <param name="children">The children.</param>
    /// <returns>The parent Entity.</returns>
    public static TEntity AddChildren<TEntity>(this TEntity parent, IEnumerable<TEntity> children)
        where TEntity : RecursiveEntity<TEntity>
    {
        children.ToList().ForEach(c => parent.AddChild(c));
        return parent;
    }
}

Once you have all of that in place, you can seed it thusly:

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

Seed Method

种子方法

/// <summary>
///     Seeds the specified context.
/// </summary>
/// <param name="context">The context.</param>
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<Category>
    {
        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);
}

#2


0  

is lazy loading on or off?,

懒惰加载打开或关闭?,

You may need to include the child relationship in the query like so

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

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