Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I dynamically construct an Expression Tree to use with anonymous types

This is my first post here. If I've broken any guidelines, please let me know and I'll be happy to correct them.

I have the following entity classes:

public class Book
{
public int BookID { get; set; }
public string Author { get; set; }
public string Publisher { get; set; }
}

And a second entity class as such,

public class Library
{
public int ID  { get; set; } 
public Book Book { get; set; }
public int Count { get; set; }
}

I also have this function to generate a lambda expression dynamically based on user input.

public static Expression<Func<T, bool>> GetLambdaExpression<T>(List<Operation> OperationList)
        {  
            ExpressionTree expressionTree = new ExpressionTree();
            Node Root = expressionTree.ConstructTree(OperationList);

            var Parameter = Expression.Parameter(typeof(T), "x");
            var Expression = CalculateExpression(Root);  //Returns an Expression Clause by And/Or every input in OperationList
            return Expression.Lambda<Func<T, bool>>(Expression, Parameter); //Finally creating Lambda
        }


Operation class contains details about the type of operation, field and values. It gets passed from the client which I'm using the query against the Entity classes by mapping field names.

code works as intended when used this way,

var OperationList = //Get from client
var LambdaExpression = GetLambdaExpression<Book>(OperationList);
var result = _BookContext.Books.Where(LambdaExpression);

OR

var OperationList = //Get from client
var LambdaExpression = GetLambdaExpression<Library>(OperationList);
var result = _LibraryContext.Library.Where(LambdaExpression);

I'm trying to Join two Entity classes using LINQ but I can't seem to find a way to dynamically create a Lambda Expression for the generated Anonymous type that is returned by the JOIN.

My join looks like,

 var result = from c in _BookContext.Books

              join d in _LibraryContext.Library

              on c.BookID equals d.ID

              select new { c , d };

However, this won't work for obvious reasons,

var OperationList = //Passed from client
var LambdaExpression = GetLambdaExpression<T>(OperationList);
result.Where(LambdaExpression); 

Passing 'object' or 'dynamic' to the GetLambdaExpression() doesn't work because the field names are not pre-defined and it throws an exception.

How could I construct an Expression Tree for an Anonymous type.

Many thanks.

UPDATE

I managed to fix it. Here's what I did:

Instead of storing the result of a join into an Anonymous type, I created a new class which has objects of the Entity classes used to perform the join.

public class JoinResult
    {
        public Book Book { get; set; }
        public Library Library { get; set; }
    }

Perform the join and store the data into JoinResult

var result = from c in _BookContext.Books

              join d in _LibraryContext.Library

              on c.BookID equals d.ID

              select new JoinResult{ Book = c , Library = d };

Finally, here's the trick to dynamically create the Lambda Expression for JoinResult.

I created an Expression Parameter for JoinResult, then created Expression Properties for the Properties of JoinResult.

I used the Expression Properties created to be used as Parameters to be passed into a new Property for the Entity class. Essentially, creating a property in the format of "x.Book.BookID".

For instance, if I wanted to perform a EqualOperation on JoinResult. Here is how I would do it.

public static IQueryable<T> PerformEqualOperation<T>(int Constant, int FieldName, Type Prop, IQueryable<T> resultOfJoin)
        {

            var Parameter = Expression.Parameter(typeof(T), "x"); //x
            PropertyInfo[] Properties = typeof(T).GetProperties(); //Get the properties of JoinResult

            string propertyname; 

            //Get the property name
            foreach(var property in Properties)
            {
               if(property.GetType() == Prop)
                  propertyname = property.Name;
            }

           //Creating a property that can be passed as a parameter to the property for Entity class.
           var expressionparameter = Expression.Property(Parameter, propertyname);  //x.Book
                var expressionproperty = Expression.Property(expressionparameter, FieldName);//x.Book.BookID
                var expressionclause = Expression.Equal(expressionproperty, Expression.Constant(Constant));//x.Book.BookID == Constant
var expressionlambda = Expression.Lambda<Func<T,bool>>(expressionclause, Parameter) 
                return resultOfJoin.Where(expressionlambda).AsQueryable();
        }

Hope this helps

like image 608
wsabimayhm Avatar asked Nov 06 '22 08:11

wsabimayhm


1 Answers

What about making an extension method? Like this:

public static class QueryExpression
{
    public static IQueryable<T> WhereWithLambdaExpression<T>(
        this IQueryable<T> query, List<Operation> OperationList)
    {
        ExpressionTree expressionTree = new ExpressionTree();
        Node Root = expressionTree.ConstructTree(OperationList);

        var Parameter = Expression.Parameter(typeof(T), "x");
        //Returns an Expression Clause by And/Or every input in OperationList
        var Expression = CalculateExpression(Root);

        //Finally creating Lambda
        Expression<Func<T, bool>> predicate =
            Expression.Lambda<Func<T, bool>>(Expression, Parameter); 

        return query.Where(predicate);
    }
}

Then

var query = joinResults.WhereWithLambdaExpression(OperationList);

The compiler can infer the anonymous type from IQueryable<T> and pass it as T to the extension method.

like image 175
weichch Avatar answered Nov 14 '22 02:11

weichch