How does Entity Framework work with recursive hierarchies? Include() seems not to work with it

C#Visual Studio-2008Entity Framework

C# Problem Overview


I have an Item. Item has a Category.

Category has ID, Name, Parent and Children. Parent and Children are of Category too.

When I do a LINQ to Entities query for a specific Item, it doesn't return the related Category, unless I use the Include("Category") method. But it doesn't bring the full category, with its parent and children. I could do Include("Category.Parent"), but this object is something like a tree, I have a recursive hierarchy and I don't know where it ends.

How can I make EF fully load the Category, with parent and children, and the parent with their parent and children, and so on?

This is not something for the whole application, for performance considerations it would be needed only for this specific entity, the Category.

C# Solutions


Solution 1 - C#

Instead of using the Include method you could use Load.

You could then do a for each and loop through all the children, loading their children. Then do a for each through their children, and so on.

The number of levels down you go will be hard coded in the number of for each loops you have.

Here is an example of using Load: http://msdn.microsoft.com/en-us/library/bb896249.aspx

Solution 2 - C#

If you definitely want the whole hierarchy loaded, then if it was me I'd try writing a stored procedure who's job it is to return all the items in a hierarchy, returning the one you ask for first (and its children subsequently).

And then let the EF's relationship fixup ensure that they are all hooked up.

i.e. something like:

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

If you've written your stored procedure correctly, materializing all the items in the hierarchy (i.e. ToList()) should make EF relationship fixup kicks in.

And then the item you want (First()) should have all its children loaded and they should have their children loaded etc. All be populated from that one stored procedure call, so no MARS problems either.

Hope this helps

Alex

Solution 3 - C#

It could be dangerous if you did happen to load all recursive entities, especially on category, you could end up with WAY more than you bargained for:

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

All of a sudden you've loaded most of your database, you could have also loaded invoices lines, then customers, then all their other invoices.

What you should do is something like the following:

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

A better solution however is to construct your query to build an anonymous class with the results so you only need to hit your datastore once.

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

This way you could return a dictionary result if required.

Remember, your contexts should be as short lived as possible.

Solution 4 - C#

Use this extension method which calls the hard-coded version of Include, to achieve a dynamic depth level of inclusion, it works great.

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

Usage:

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

Anyway, meanwhile, join the discussion about it on the EF repo.

Solution 5 - C#

You don't want to do recursive loading of the hierarchy, unless you are allowing a user to iteratively drill down/up the tree: Every level of recursion is another trip to the database. Similarly, you'll want lazy loading off to prevent further DB trips as you're traversing the hierarchy when rendering to a page or sending over a webservice.

Instead, flip your query: Get Catalog, and Include the items in it. This will get you all items both hierarchically (navigation properties) and flattened, so now you just need to exclude the non-root elements present at the root, which should be pretty trivial.

I had this problem and provided a detailed example of this solution to another, here

Solution 6 - C#

You chould rather introduce a mapping table that maps each Category a parent and a child, instead of adding the parent and child property to the cargo itself.

Depending on how often you need that information it can be queried on demand. Via unique constraints in the db you can avoid an infinite amount of relationships beeing possible.

Solution 7 - C#

I found out that if you include "two parent levels", you will get the whole parent hierarchy, like that:

var query = Context.Items
            .Include(i => i.Category)
            .Include(i => i.Category.Parent.Parent)

Solution 8 - C#

And now for a completely different approach to hierarchical data, for example populating a treeview.

First, do a flat query for all data, and then build the object graph in memory:

  var items = this.DbContext.Items.Where(i=> i.EntityStatusId == entityStatusId).Select(a=> new ItemInfo() { 
			Id = a.Id,
			ParentId = a.ParentId,
			Name = a.Name,
			ItemTypeId = a.ItemTypeId
			}).ToList();

Get the root item:

 parent = items.FirstOrDefault(a => a.ItemTypeId == (int)Enums.ItemTypes.Root);

Now build your graph:

 this.GetDecendantsFromList(parent, items);


 private void GetDecendantsFromList(ItemInfo parent, List<ItemInfo> items)
	{
		parent.Children = items.Where(a => a.ParentId == parent.Id).ToList();
		foreach (var child in parent.Children)
		{
			this.GetDecendantsFromList(child,items);
		}
	}

Solution 9 - C#

Here is a clever recursive function I found here that would work for this:

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

Solution 10 - C#

You could also create a tablevalued function in the database and add that to your DBContext. Then you can call that from your code.

> This example requires that you import EntityFramework.Functions from > nuget.

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

Solution 11 - C#

try this

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;

Solution 12 - C#

@parliament gave me an idea for EF6. Example for Category with Methods to load all parents up to root node and all children.

NOTE: Use this only for non performance critical operation. Example with 1000 nodes performance from http://nosalan.blogspot.se/2012/09/hierarchical-data-and-entity-framework-4.html.

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

Code:

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

Solution 13 - C#

My suggestion would be

var query = CreateQuery()
    .Where(entity => entity.Id == Id)
    .Include(entity => entity.Parent);
var result = await FindAsync(query);
    
return result.FirstOrDefault();

and it means it will load single entity and all this entity.Parent entities recursive.

entity is same as entity.Parent

Solution 14 - C#

public static class EntityFrameworkExtensions
{
    public static ObjectContext GetObjectContext(this DbContext context) 
    {
        ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;

        return objectContext;
    }

    public static string GetTableName<T>(this ObjectSet<T> objectSet) 
        where T : class
    {
        string sql = objectSet.ToTraceString();
        Regex regex = new Regex("FROM (?<table>.*) AS");
        Match match = regex.Match(sql);

        string table = match.Groups["table"].Value;
        return table;
    }

    public static IQueryable<T> RecursiveInclude<T>(this IQueryable<T> query, Expression<Func<T, T>> navigationPropertyExpression, DbContext context)
        where T : class
    {
        var objectContext = context.GetObjectContext();

        var entityObjectSet = objectContext.CreateObjectSet<T>();
        var entityTableName = entityObjectSet.GetTableName();
        var navigationPropertyName = ((MemberExpression)navigationPropertyExpression.Body).Member.Name;

        var navigationProperty = entityObjectSet
            .EntitySet
            .ElementType
            .DeclaredNavigationProperties
            .Where(w => w.Name.Equals(navigationPropertyName))
            .FirstOrDefault();

        var association = objectContext.MetadataWorkspace
            .GetItems<AssociationType>(DataSpace.SSpace)
            .Single(a => a.Name == navigationProperty.RelationshipType.Name);

        var pkName = association.ReferentialConstraints[0].FromProperties[0].Name;
        var fkName = association.ReferentialConstraints[0].ToProperties[0].Name;

        var sqlQuery = @"
                EXEC ('
                	;WITH CTE AS
                	(
                	    SELECT 
                			[cte1].' + @TABLE_PK + '
                	        , Level = 1
                	    FROM ' + @TABLE_NAME + ' [cte1]
                	    WHERE [cte1].' + @TABLE_FK + ' IS NULL
                	    
                		UNION ALL
                	    
                		SELECT 
                			[cte2].' + @TABLE_PK + '
                	        , Level = CTE.Level + 1
                	    FROM ' + @TABLE_NAME + ' [cte2]
                	        INNER JOIN CTE ON CTE.' + @TABLE_PK + ' = [cte2].' + @TABLE_FK + '
                	)
                	SELECT 
                		MAX(CTE.Level)
                	FROM CTE 
                ')
            ";

        var rawSqlQuery = context.Database.SqlQuery<int>(sqlQuery, new SqlParameter[]
            {
                new SqlParameter("TABLE_NAME", entityTableName),
                new SqlParameter("TABLE_PK", pkName),
                new SqlParameter("TABLE_FK", fkName)
            });

        var includeCount = rawSqlQuery.FirstOrDefault();

        var include = string.Empty;

        for (var i = 0; i < (includeCount - 1); i++)
        {
            if (i > 0)
                include += ".";

            include += navigationPropertyName;
        }

        return query.Include(include);
    }
}

Solution 15 - C#

Let me offer my simple solution that fits needs to enable/disable the branch of hierarchical data of the selected department's structure of an organization.

The table Departments looks according this SQL

CREATE TABLE [dbo].[Departments](
	[ID] [int] IDENTITY(1,1) NOT NULL,
	[Name] [nvarchar](1000) NOT NULL,
	[OrganizationID] [int] NOT NULL,
	[ParentID] [int] NULL,
	[IsEnabled] [bit] NOT NULL, 
 CONSTRAINT [PK_Departments] PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

C# code provides a very simple approach that work fine for me.

  1. It returns the complete table asynchronously.

  2. It changes property for the linked rows.

    public async Task RemoveDepartmentAsync(int orgID, int depID) { try { using (var db = new GJobEntities()) { var org = await db.Organizations.FirstOrDefaultAsync(x => x.ID == orgID); // Check if the organization exists if (org != null) { var allDepartments = await db.Departments.ToListAsync(); // get all table items var isExisting = allDepartments.FirstOrDefault(x => x.OrganizationID == orgID && x.ID == depID); if (isExisting != null) // Check if the department exists { isExisting.IsEnabled = false; // Change the property of visibility of the department var all = allDepartments.Where(x => x.OrganizationID == orgID && x.ID == isExisting.ID).ToList(); foreach (var item in all) { item.IsEnabled = false; RecursiveRemoveDepartment(orgID, item.ID, ref allDepartments); // Loop over table data set to change property of the linked items } await db.SaveChangesAsync(); } return true; } } } catch (Exception ex) { logger.Error(ex); }

                 return false;
             }
     
             private void RecursiveRemoveDepartment(int orgID, int? parentID, ref List<Department> items)
             {
                 var all = items.Where(x => x.OrganizationID == orgID && x.ParentID == parentID);
                 foreach (var item in all)
                 {
                     item.IsEnabled = false;
                     RecursiveRemoveDepartment(orgID, item.ID, ref items);
                 }
             }
    

This approach works very fast for relative small amount of records I guess less 100000. Probably for big set of data you have to implement server side stored function.

Enjoy!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVictor RodriguesView Question on Stackoverflow
Solution 1 - C#Shiraz BhaijiView Answer on Stackoverflow
Solution 2 - C#Alex JamesView Answer on Stackoverflow
Solution 3 - C#Brett RyanView Answer on Stackoverflow
Solution 4 - C#Shimmy WeitzhandlerView Answer on Stackoverflow
Solution 5 - C#JoeBrockhausView Answer on Stackoverflow
Solution 6 - C#Johannes RudolphView Answer on Stackoverflow
Solution 7 - C#Francisco CardosoView Answer on Stackoverflow
Solution 8 - C#Greg GumView Answer on Stackoverflow
Solution 9 - C#parliamentView Answer on Stackoverflow
Solution 10 - C#OzzianView Answer on Stackoverflow
Solution 11 - C#tobiasView Answer on Stackoverflow
Solution 12 - C#OgglasView Answer on Stackoverflow
Solution 13 - C#aursadView Answer on Stackoverflow
Solution 14 - C#Hugo BrunholiView Answer on Stackoverflow
Solution 15 - C#NoWarView Answer on Stackoverflow