5

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

6
  • 2
    I don't think it's necessarily a good idea to be using anonymouse types here, but you could fudge it by making GetLambdaExpression an extension method of IQueryable<T>. Commented Mar 30, 2020 at 8:58
  • There are ways to create lambda expression with anonymous type and pass into Enumerable.Where, but you wouldn't be able to use the return result as IEnumerable<anonymous type> in your code. Instead, you will get IEnumerable<object>. If you need to access the properties, maybe try using ValueTuple, and your existing code should work. Commented Mar 30, 2020 at 9:56
  • 1
    This is your first question - respect. Your operations have to contain any information about the requested type. Maybe you can change your linq to something like from c in _BookContext.Books.Where(GetLambdaExpression<Book>(OperationList)) join d in _LibraryContext.Library.Where(GetLambdaExpression<Library>(OperationList)) on ... I've not tested it - it's just a hint. This will only work if your GetLambdaExpression-method can extract the interesting part for the requested type. Commented Mar 30, 2020 at 9:57
  • @weichch I'm not exactly sure but OP is using entity framework and as far as I know it doesn't support ValueTuple. Commented Mar 30, 2020 at 9:59
  • @SebastianSchumann thank you! I've considered separating the query for book and library and constructing the expressions individually, but if I had an expression that was ment to be like this I don't think it helps to separate the queries: (Book.ID == 'x' && Library.ID== 'y') || (Book.Author == "author" || Library.Book == "Book") I'm not sure how I would create a condition that would input "Library.ID=='y'" to be && with Book.ID=='x'. Constructing them individually would return Book.ID even if Library.ID condition was false. Any thoughts? Commented Mar 30, 2020 at 10:15

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

This will not work because CalculateExpression() does this: Parameter = Expression.Parameter(typeof(T), "x"); Property = Expression.Property(Parameter, FeildName); The Field Name is a string like 'ID' or 'BookID' which I've defined in the Book and Library classes. When I pass an Anon type for <T> into the function I get an Error that, the Property 'ID' or 'BookID' doesn't exist in this context
@wsabimayhm I don't get it. Your question seems to be building expression for anonymous type which is new {c, d}, but CalculateExpression does not work with that anonymous type? I thought your CalculateExpression is supposed to build something like this x.c.ID rather than x.ID?
no it doesn't do that. Type needs to be pre-defined before I can call CalculateExpression() so that it can assign the property which exists in the entity class. I'm still new at this, could you tell me if it is possible to create an Expression in the format of x.c.ID, i've tried this and I don't think its possible. what do you think?
nvm i figured it out. you can just pass a property as a param to another expression property to get x.c.ID. so silly lol thanks much for the help :D

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.