How to map recursive relation on self in Entity Framework code-first approach

asp.net asp.net-mvc asp.net-mvc-4 c# entity-framework

Question

I just want to make a simple recursive category. If category is the rootRootCategory_Id is set to null and, if it is set to any id, it belongs to a different category. Two kid categories have been included as a category inSeed() technique to test, and it is ineffective. (I checked the database later; there are new entries.)

model category

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 approach

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 verified that it is ineffective.

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

An image of what I wish to accomplish

this was produced using linq-to-sql and a database-first strategy.
enter image description here

1
10
9/29/2012 10:40:56 PM

Accepted Answer

I really didn't want to resurrect this topic, but I've been looking for solutions to this specific issue.

Here is my long-winded, but far more scalable, answer for the problem of Code First programming. Additionally, the Strategy pattern is introduced in order to support SoC while maintaining the lowest possible POCO.

Create the entity primitives and interfaces as a first step.

IEntity Interface:

/// <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; }
}

Interface for Recursive Entities:

/// <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; }
}

Abstract Entity 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; }
}

Abstract Class for 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: Regarding this lesson, several individuals have recommended changes to this post. The class must only permitRecursiveEntity<TEntity> as a restriction, instead ofIEntity It can only handle recursive things as a result. This lessens the occurrence of type mismatch exceptions. If you employIEntity Instead, you'll need to include some exception handling to deal with base type conflicts.

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 think you get the picture. 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.

Additionally, when tested, it didn't seem to get along withIRecursiveEntity<TEntity> either. The way it is written here is entirely legitimate and functional, and I recall making adjustments to make sure it worked as planned. I may come back to this soon since I'm updating a previous project that utilizes it. I believe there was a compromise between inheritance heirarchy and code beauty when utilizing a higher level class required casting several properties betweenIEntity and IRecursiveEntity<IEntity> which, in turn, performed worse and had a bad appearance.

Derive the RecursiveEntity in step two.

I've utilized the illustration from the first query.

Concrete Category 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; }
}

All except the non-derived attributes have been removed from the class. TheCategory all of its other characteristics are derived from its self-associated generic inheritance of theRecursiveEntity class.

Extension Methods in Step 3 (Optional).

I've included additional extension methods that allow you to quickly add new children to any parent item to make the entire thing more manageable. The problem, I discovered, was that you needed to establish both ends of the one-to-many connection since you couldn't manage the kid in the manner that was intended by just adding them to the list. It's a quick repair that, over time, saves a ton of time.

Static Class for 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've done that, you may seed it as follows:

Seed Approach

/// <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);
}
10
1/12/2015 7:02:24 PM

Popular Answer

Does lazy loading exist or not? ,

The kid connection may need to be mentioned in the question in the following way:

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


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow