Search code examples
entity-frameworklinqlinq-to-sqlmany-to-manyexpression-trees

Build an Expression Tree for Entity Framework Many to Many relationship?


I'm working on a Code for the following structure:
3 Tables

Table "Book", "BookAuthor" and "Author".

I now want to build up an ExpressionTree to get the following Linq-result:

_ent.Book.Where(c => c.BookAuthor.Any(cd => cd.Author.AuthorName == "test"));

My Problem is the last Expression to get to cd.Author.AuthorName (or similar).

My current code:

private MethodCallExpression BuiltMethodCall(IQueryable _query, string CustTypeID, Type _ParentObjType,
Type _ChildObjType, string strChildObj, string strChildCol)
    {

        //This function will build a dynamic linq expression tree representing the ling calls of:
        //Book.Where(c => c.BookAuthor.Any(cd => cd.Author = custTypeID))

        ConstantExpression value = Expression.Constant(CustTypeID);

        //Build the outer part of the Where clause
        ParameterExpression parameterOuter = Expression.Parameter(_ParentObjType, "c");
        MemberExpression propertyOuter = Expression.Property(parameterOuter, strChildObj);

        //Build the comparison inside of the Any clause
        ParameterExpression parameterInner = Expression.Parameter(_ChildObjType, "cd");
        MemberExpression propertyInner = Expression.Property(parameterInner, strChildCol);

        BinaryExpression comparison = Expression.Equal(propertyInner, value);
        LambdaExpression lambdaInner = Expression.Lambda(comparison, parameterInner);

        //Create Generic Any Method
        Func<MethodInfo, bool> methodLambda = m => m.Name == "Any" && m.GetParameters().Length == 2;
        MethodInfo method = typeof(Enumerable).GetMethods().Where(methodLambda).Single()
            .MakeGenericMethod(_ChildObjType);

        //Create the Any Expression Tree and convert it to a Lambda
        MethodCallExpression callAny = Expression.Call(method, propertyOuter, lambdaInner);
        LambdaExpression lambdaAny = Expression.Lambda(callAny, parameterOuter);

        //Build the final Where Call
        MethodCallExpression whereCall = Expression.Call(typeof(Queryable), "Where",
            new Type[] { _query.ElementType }, new Expression[]
            {
                    _query.Expression,
                    Expression.Quote(lambdaAny)
            });

        return whereCall;
    }

Solution

  • If I understand correctly, you are asking how to create nested property accessor expression.

    It's quite easy with the help of String.Split and Enumerable.Aggregate methods.

    Here is a custom extension method which can be used as Expression.Property method replacement:

    public static partial class ExpressionExtensions
    {
        public static MemberExpression Property(this Expression instance, string path)
        {
            return (MemberExpression)path.Split('.').Aggregate(instance, Expression.Property);
        }
    }
    

    It handles correctly both simple and nested properties encoded in string with . separator (e.g. "BookAuthor" or "Author.AuthorName").

    So instead of

    MemberExpression propertyOuter = Expression.Property(parameterOuter, strChildObj);
    

    and

    MemberExpression propertyInner = Expression.Property(parameterInner, strChildCol);
    

    you can safely use

    MemberExpression propertyOuter = parameterOuter.Property(strChildObj);
    

    and

    MemberExpression propertyInner = parameterInner.Property(strChildCol);