81

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.

Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
Victor Rodrigues
  • 11,353
  • 23
  • 75
  • 107
  • 2
    If it's recursive, then it could (easily) have a cycle. You **have** to choose a max depth. Having done that, you can write a query for it. – Craig Stuntz Aug 20 '09 at 20:24
  • 3
    No, cycles will not be allowed. The business layers see this as a tree, no chance to the parent add a child as parent. – Victor Rodrigues Aug 20 '09 at 20:41
  • If cycles are not allowed then the best relational model might be nested sets. Depends on your app, though. NS is good for mostly querying, FKs are good for mostly insert/update/delete. But the mapping would be entirely different. – Craig Stuntz Aug 21 '09 at 01:53
  • Hmm, why do I need to change this model? It sounds pretty good to work, unless Entity Framework doesn't allow me to do eager loading without doing "Category.Parent.Parent.Parent....". I wrote a recursive method that does something like " if(!category.ParentReference.IsLoaded) category.ParentReference.Load(); ", but it didn't work, a data reader error was thrown. If I have the EF support, I don't see how this category could be easier implemented with NS. – Victor Rodrigues Aug 21 '09 at 12:33
  • 2
    I don't know that you need to change the model. But nested sets are significantly faster when doing primarily selection. RDBMSs usually either (1) have no support for heirarchies at all (e.g., SQL 2005) or (2) have it, to some degree, but don't expose it in their EF provider (e.g., SQL 2008). You're asking the EF to support something the DB cannot do in straight SQL! (Think: what SQL statement would return JOINed rows with unlimited depth?) Eager loading in the EF works by changing the SQL statement. What SQL statement would produce the result set you want? – Craig Stuntz Aug 21 '09 at 13:08
  • 2
    Just because your implementation doesn't allow for cycles, the data constructs you are using could. Thus EF can't allow you to eagerly load the tree. Instead, you need to explicitly call Load when you need the related items. Alternatively, you could load the table as a flat collection and re-build the tree manually. While that would issue a single request to the database for reading, it becomes more tricky managing updates. – Jim Wooley Aug 22 '09 at 17:56
  • Check [my answer](http://stackoverflow.com/a/33151553/75500) which provides you with an extension method that loads the tree up to a depth level of your choice, in one round-trip to server. – Shimmy Weitzhandler Dec 15 '15 at 22:58

15 Answers15

26

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

Ken Keenan
  • 9,818
  • 5
  • 32
  • 49
Shiraz Bhaiji
  • 64,065
  • 34
  • 143
  • 252
  • I've tried Load, but a SqlDataReader exception was thrown (even when enabling the MultipleActiveResultSets) – Victor Rodrigues Sep 01 '09 at 13:55
  • The problem was SQL Server 2000. It does not support MARS. – Victor Rodrigues Sep 02 '09 at 14:10
  • 46
    This is a really bad solution for performance reasons: each level is another trip to the database, and it gets even worse if you have lazy-loading enabled. more here: http://stackoverflow.com/a/22024714/237723 – JoeBrockhaus Apr 15 '14 at 15:19
  • @reggaeguitar This question was flooded with not-so-great answers. Copying and pasting the same code from the more thorough answer to another question would be wasteful. Also 18 > 5 https://stackoverflow.com/questions/2266473/how-to-do-recursive-load-with-entity-framework/17346650#17346650 – JoeBrockhaus Jun 22 '17 at 05:06
  • 1
    @JoeBrockhaus I was referring to the msdn link – reggaeguitar Jun 22 '17 at 22:06
  • touche, my bad :-x – JoeBrockhaus Jun 23 '17 at 03:36
14

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

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Alex James
  • 20,874
  • 3
  • 50
  • 49
  • 1
    This will only work if your SPROC is mapped to the Category Entity in your Function Import properties window, and only returns that data. ie: If your SPROC performs a join with `EntityTableA` and `EntityTableB` & your goal is to have a result collection of `EntityTableA` entities with Navigation Properties to `EntityTableB` entities, you will NOT be able to using this method. Instead, you'll have to Group your flattened set of data accordingly and convert into the respective entities. – JoeBrockhaus Dec 19 '14 at 21:15
  • 1
    If you use SQL Server and go for the SP solution, you can use CTE to get the hierarchy in one query. See: https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx – Assaf S. Feb 26 '18 at 13:59
7

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.

Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
  • This is great. And the discussion is awesome. However, I'm unsure how to use your Expression in this case. Even with your example, if you use Include(4, m => m.Referred), that won't work? If your Queryable is IQueryable, then your Func return value will be an array? – jsgoupil Dec 15 '15 at 20:38
  • @jsgoupil, no. As you can see, the return value would be of the exact type as the input parameter `IQueryable`, and applies for deferred execution. You can use this as a fluent API query and any of the `Inclde` extension methods many times on a single query to include various branches in the return data. – Shimmy Weitzhandler Dec 15 '15 at 22:51
  • I'm sorry, I'm a bit at a loss. Can you provide an example on how to use it shortly with class Affiliate example? – jsgoupil Dec 17 '15 at 06:46
  • 1
    @jsgoupil I've updated my answer per your request. Should anything be unclear talk to me. the argument `3` for the `levelIndex` parameter states the depth level of which we want to load the children of. – Shimmy Weitzhandler Dec 17 '15 at 07:59
  • Thanks, that makes sense. I got confused earlier because the example in other answers were talking about "Children", and not "Child" like in your example. Thanks for putting the usage here. – jsgoupil Dec 17 '15 at 23:09
6

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.

Brett Ryan
  • 26,937
  • 30
  • 128
  • 163
  • 2
    This results in lots of round trips to the database, which eager loading is intended to avoid. – Asad Saeeduddin Sep 17 '14 at 13:44
  • 2
    That's precisely why I said "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", eager loading as I pointed out is not always a good solution. Note also, that your referential data will load only once, resulting in less hits during the context lifetime. – Brett Ryan Sep 17 '14 at 16:59
  • 1
    Ah I see; sorry about the downvote then. I just saw the "this is what you should do" part, followed by what I felt the OP shouldn't do. If you edit the answer (perhaps qualify "this is what you should do"?) I'll be able to reverse my vote. – Asad Saeeduddin Sep 17 '14 at 17:08
6

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

Community
  • 1
  • 1
JoeBrockhaus
  • 2,745
  • 2
  • 40
  • 64
3

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.

Johannes Rudolph
  • 35,298
  • 14
  • 114
  • 172
3

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)
Francisco Cardoso
  • 1,438
  • 15
  • 20
  • I suspect this worked because every category is associated with an item or has a grandchild associated with an item. When you load up ALL the items into context you get all the categories and the relationship magic in the context connected them all. It didn't work for me when I tried to get only one "item" so this is a special case of the answers above that rely on "first load the entire hierarchy into memory", which works but is a bit painful for what's really a pretty simple recursive sql query. EF should have this built in. – Jon Clayton Jul 14 '21 at 19:50
2

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);
        }
    }
Greg Gum
  • 33,478
  • 39
  • 162
  • 233
1

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;
            }
    }
}
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
parliament
  • 21,544
  • 38
  • 148
  • 238
  • 2
    This is great for an in-memory collection, but I'd not recommend this for active result retrieval from a database. n+1 FTL http://stackoverflow.com/questions/97197/what-is-the-n1-selects-issue – JoeBrockhaus Jan 13 '15 at 17:20
1

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;
    }
}
Ozzian
  • 11
  • 1
0

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;
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
tobias
  • 1,502
  • 3
  • 22
  • 47
0

@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;
        }
    }   
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418
0

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
coder
  • 8,346
  • 16
  • 39
  • 53
aursad
  • 1
  • 1
0
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);
    }
}
0

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<bool> 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!

NoWar
  • 36,338
  • 80
  • 323
  • 498