5

I'm trying to build a nested query using Linq/LinqKit. In theory this seems to be easy. But I am stuck with the practical part.

In my database I have a table which has a self-reference to its parent. In my linq-query I now want to select all parents of a given element (and the parents of this one and so on).

In my code I have the following expression in partial class of MyTable:

public static Expression<Func<MyTable, IEnumerable<MyTable>>> Parents => (entity) => entity.ParentId != null ? new[]{entity.ParentEntity}.Union(Parents.Invoke(entity.ParentEntity) : new MyEntity[]{};

which should select the parent of a given entity and those parents when the ParentId is set.

The query itself (simplified):

dbContext
    .MyTable
    .AsExpandable()
    .Where(x => x.Id == myId)
    .Select(x => new
    {
        Parents = MyTable.Parents.Invoke(x, dbContext)
    });

Running this code ends up in an StackOverflowException as the stop-condition is not hit and therefore the Parents-call is nested endlessly until the stack is full.

Any ideas how this can be done or is this not possible? Or is there an other way for fetching nested data using Linq/LinqKit within one query?

I already tried passing the context to the expression in order to create a sub-query (also not working):

public static Expression<Func<MyTable, MyContext, IEnumerable<MyTable>>> Parents => (entity, dbContext) => entity.ParentId != null ? new[]{entity.ParentEntity}.Union(Parents.Invoke(dbContext.MyTable.FirstOrDefault(x => x.Id == entity.ParentId), dbContext) : new MyEntity[]{};
KingKerosin
  • 3,639
  • 4
  • 38
  • 77
  • Hi, interesting attempt! But no, it's not possible to create expandable (EF compatible) recursive expression with unknown max level of nesting since there is no equivalent of SQL CTE. – Ivan Stoev Dec 15 '16 at 09:31
  • @IvanStoev, What if I would knew the maximum level of nesting? Already tried passing a depth param which is decremented by each recursive call. But same exception here – KingKerosin Dec 15 '16 at 09:33

1 Answers1

2

As mentioned in comments, currently it's not possible to create a recursive expandable (i.e. non invokable) expression.

However, if you can limit the maximum depth, one possible solution would be to build expression like this (utilizing the EF navigation property):

Parents = new MyTable [] { x.Parent, x.Parent.Parent, x.Parent.Parent.Parent, ...}
    .Where(e => e != null)

dynamically:

static Expression<Func<MyTable, IEnumerable<MyTable>>> ParentsSelector(int maxLevels)
{
    var parameter = Expression.Parameter(typeof(MyTable), "x");
    var parents = new Expression[maxLevels];
    for (int i = 0; i < parents.Length; i++)
        parents[i] = Expression.Property(i > 0 ? parents[i - 1] : parameter, "Parent");
    Expression<Func<MyTable, bool>> predicate = x => x != null;
    var result = Expression.Call(
        typeof(Enumerable), "Where", new[] { parameter.Type },
        Expression.NewArrayInit(parameter.Type, parents), predicate);
    return Expression.Lambda<Func<MyTable, IEnumerable<MyTable>>>(result, parameter);
}

and use it as follows:

var parents = ParentsSelector(10);
var query = dbContext.MyTable
    .AsExpandable()
    .Where(x => x.Id == myId)
    .Select(x => new
    {
        Parents = parents.Invoke(x)
    });
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343