7
\$\begingroup\$

In Entity Framework Core we can have recursive entities. But we cannot do an "Include" for these recursives (can we?). So I wrote a Get-method that does it for me:

First, this is my RecursiveEntity<T> base-class:

public class Entity : IEntity
{
    [Key]
    public int Id { get; set; }
}

public abstract class RecursiveEntity<TEntity> : Entity, IRecursiveEntity<TEntity> 
    where TEntity : RecursiveEntity<TEntity>
{
    public virtual TEntity Parent { get; set; }
    public virtual ICollection<TEntity> Children { get; set; }
}

And this is my Repository-base-class for recurisve entities:

public abstract class RecursiveRepository<T, TDataContext> 
    where T : RecursiveEntity<T> where TDataContext : DbContext
{
    protected IEnumerable<T> Get(Expression<Func<T, bool>> expression)
    {
        IQueryable<T> parents = DataContext.Set<T>()
            .Include(x => x.Children)
            .Where(w => w.Parent == null)
            .Where(expression);

        foreach (T entity in parents)
        {
            if (entity.Children != null && entity.Children.Any())
                entity.Children = _getChildren(entity, expression).ToList();

            yield return entity;
        }
    }

    private IEnumerable<T> _getChildren(T parentEntity, Expression<Func<T, bool>> expression)
    {
        IQueryable<T> children = DataContext.Set<T>()
            .Include(x => x.Parent)
            .Where(w => w.Parent != null && w.Parent.Id == parentEntity.Id)
            .Where(expression);

        foreach (T entity in children)
        {
            entity.Children = _getChildren(entity, expression).ToList();
            yield return entity;
        }
    }
}

Any suggestion what I can do better? Is it a good solution to do that many DataContext<Set>-accesses and assign them to the parent with ToList()?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Can there be cycles in this recursive entity? For instance, can my parent be my grandchild? \$\endgroup\$
    – dfhwze
    Jun 5, 2019 at 10:40
  • \$\begingroup\$ @dfhwze good point! regarding database, yes, it's possible. I should handle that in my backend - no, I dont want circular references. \$\endgroup\$ Jun 5, 2019 at 10:42
  • 1
    \$\begingroup\$ I don't know whether EF supports dynamic hierarchical loading. it didn't in the past. However, one way to try and solve this it is to load the objects as a flattened list and then build the hierarchy in memory. \$\endgroup\$
    – dfhwze
    Jun 5, 2019 at 10:51

1 Answer 1

8
\$\begingroup\$

If you remove virtual from Children it'll work with Include(e => e.Children):

  public abstract class RecursiveEntity<TEntity>
    : Entity, IRecursiveEntity<TEntity>
    where TEntity : RecursiveEntity<TEntity>
  {
    public virtual TEntity Parent { get; set; }
    public ICollection<TEntity> Children { get; set; }
  }

The meaning with virtual is to provide lazy loading. If you can't live with that, you'll have to do it manually, as you do.


Alternatively I think, you can add "MultipleActiveResultSets=True" to your connection string.


UPDATE

I seems that EF 6 and EFCore work differently when it comes to Include?

EFCore has the Include() and ThenInclude pattern but that is rather useless for recursive initialization.

When loading manually have you then experimented with the abilities to load navigation properties on each object as in:

public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity: RecursiveEntity<TEntity>
{
  foreach (TEntity entity in Set<TEntity>().Where(e => e.Parent == null).Where(filter))
  {
    GetChildren(entity, filter);
    yield return entity;
  }
}

private void GetChildren<TEnity>(TEnity parent, Expression<Func<TEnity, bool>> childFilter) where TEnity : RecursiveEntity<TEnity>
{
  Entry(parent).Collection(e => e.Children).Query().Where(childFilter).Load();
  // Entry(parent).Reference(e => e.Parent).Load(); // I think this shouldn't be necessary because loading the children will load the parent on them

  if (parent.Children != null)
  {
    foreach (TEnity child in parent.Children)
    {
      GetChildren(child, childFilter);
    }
  }
}

It should produce the same result as yours, but is maybe a little clearer and in line with the EF concept.

\$\endgroup\$
5
  • \$\begingroup\$ would be nice - I tried, but in my case, it only loads the direct children. not the children of the children.. \$\endgroup\$ Jun 5, 2019 at 12:34
  • \$\begingroup\$ @MatthiasBurger: Strange it works in EF 6 \$\endgroup\$
    – user73941
    Jun 5, 2019 at 13:44
  • \$\begingroup\$ I heared that in EF core, the "virtual" is not used anymore like in EF 6. And imho EF Core is not a "follower" of EF 6. Maybe this is not implemented. But I'll test it again later \$\endgroup\$ Jun 5, 2019 at 13:50
  • \$\begingroup\$ So the reverse (child to parents) should be Db.Entry(child).Reference(c=>c.Parent).Query().Where(filter).Load(); ???? \$\endgroup\$ Sep 13, 2021 at 6:30
  • \$\begingroup\$ thank you for this solution, that helped me a bunch! \$\endgroup\$ Mar 9, 2022 at 23:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.