How do you create a System.Linq.Expressions.Expression object containing a call to Any ()

I want to dynamically generate the linq.expressions.expression operator, which I can use as a filter.

Here is an example Linq query that I would like to convert to an expression:

ctx.customer.where(c=>ctx.Invoice.Any(i=>i.customerId == c.id)); 

Here is my attempt

 using System.Linq.Expressions; var c = Expression.parameter(typeof(Customer),"c"); var i = Expression.parameter(typeof(Invoice),"i"); var rightPart= Expression.Equal( Expression.propertyorField(i,"customerId"), Expression.propertyorfield(c,"id") 

Please, help.

+4
source share
4 answers

When I need to manually create a linq expression, I simply create .Net, create such an expression for me from lambda, and then I can examine its structure. For example, run debug

 Expression<Func<TestObject, bool>> expression = t => t.Invoice.Any(i => i.CustomerId == t.Id); 

and check the expression variable.

+6
source

I assume that LinqExpression comes from using LinqExpression = System.Linq.Expressions.Expression; .

There is no specific type of expression for Any , since it is not an operator, etc. Any is a static method, so you need to create a Call expression for this.

You will need to create an expression based on the syntax of the static method, not the syntax of the extension method:

 ctx.customer.Where(c => Enumerable.Any(ctx.Invoice, i => i.customerId == c.id)); 

There are many steps to this. Here it is outlined, because I do not have time to work out all the steps correctly. I'm not quite sure how to imagine that the parameter c used in the internal lambda is not a parameter of this lambda, but rather an external lambda. In general terms you need

  • Check out MethodInfo to properly overload the generic Enumerable.Any method.
  • Build a non-generic MethodInfo from a generic one (i.e. call MakeGenericMethod ).
  • Build a PropertyExpression expression that you pass as the first argument to the method (representing ctx.Invoce )
  • Build the body of the lambda expression that you pass as the second argument to the method (i.e. rightPart in your code example).
  • Build a LambdaExpression expression (e.g. var innerLambda = Expression.Lambda(rightPart, i); )
  • Build a MethodCallExpression expression representing a method call by passing MethodInfo and innerLambda.
  • Create a LambdaExpression expression that you will go to Where (i.e. Expression.Lambda(methodCallExpression, c) .

The fourth step will be different if you want to combine boolean expressions as indicated in your comment.

I hope this helps.

+5
source

Note that I have added an internal expression. There is some closing code that you will need to do as you have a captured variable.

Pay attention to the possibility of simplification when using the code fooobar.com/questions/125802 / ....

Minimal code to get expression code in DebugView ...

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Linq.Expressions; namespace ConsoleApplication7 { class CTX { public List<Customer> Invoices = new List<Customer>(); } class Customer { public int id; public static bool HasMatchingIds(Customer first, Customer second) { return true; } } class Program { static void Main(string[] args) { CTX ctx = new CTX(); Expression<Func<Customer, bool>> expression = cust => ctx.Invoices.Any(customer => Customer.HasMatchingIds(customer, cust)); } } } 

Here is what I see in the reflector:

 private static void Main(string[] args) { ParameterExpression CS$0$0000; ParameterExpression CS$0$0002; CTX ctx = new CTX(); Expression<Func<Customer, bool>> expression = Expression.Lambda<Func<Customer, bool>>( Expression.Call(null, (MethodInfo) methodof(Enumerable.Any), new Expression[] { Expression.Constant(ctx.Invoices), Expression.Lambda<Func<Customer, bool>>( Expression.Call(null, (MethodInfo) methodof(Customer.HasMatchingIds), new Expression[] { CS$0$0002 = Expression.Parameter(typeof(Customer), "customer"), CS$0$0000 = Expression.Parameter(typeof(Customer), "cust") }), new ParameterExpression[] { CS$0$0002 }) }), new ParameterExpression[] { CS$0$0000 }); } 

Close enough for government work ... This tells me that this is far from trivial, and you need to simplify your initial request.

I would also try running LinqPad for rapid prototyping.

+2
source

Add this code:

 var any = Expression.Call(typeof(Queryable), "Any", new Type[] { typeof(Invoice) }, Expression.PropertyOrField(Expression.Constant(ctx), "Invoice"), Expression.Lambda(rightPart, i)); var filter = Expression.Lambda<Func<Customer, bool>>(any, c); 

You can then use filter as a parameter in any IQueryable<Customer>.Where .

+1
source

Source: https://habr.com/ru/post/1403685/


All Articles