How do you make a System.Linq.Expressions.Expression object containing a call to Any()
Solution 1
When I need to manually create linq expression I just make .Net create for me such expression from lambda and then I can explore its structure. For example run under debug
Expression<Func<TestObject, bool>> expression = t => t.Invoice.Any(i => i.CustomerId == t.Id);
and inspect expression variable.
Solution 2
I assume that LinqExpression
comes from using LinqExpression = System.Linq.Expressions.Expression;
.
There is no specific expression type for Any
, because it is not an operator or the like. Any
is a static method, so you should create a Call
expression for that.
You will need to build the expression based on the static method syntax rather than the extension method syntax:
ctx.customer.Where(c => Enumerable.Any(ctx.Invoice, i => i.customerId == c.id));
There are a lot of steps to this. Here it is in outline, because I don't have time to work out all of the steps correctly. I'm not entirely sure how to represent the fact that the c
parameter used in the inner lambda is not a parameter of that lambda, but rather of the outer lambda. In outline, you'll need to
- Retrieve the MethodInfo for the correct overload of the generic
Enumerable.Any
method. - Construct the non-generic MethodInfo from the generic one (i.e., call
MakeGenericMethod
). - Construct the PropertyExpression that you will pass as the first argument to the method (representing
ctx.Invoce
) - Construct the body of the lambda expression that you will pass as the second argument to the method (i.e.,
rightPart
in your example code). - Construct the LambdaExpression (e.g.,
var innerLambda = Expression.Lambda(rightPart, i);
) - Construct the MethodCallExpression representing the call to the method, passing the MethodInfo and innerLambda.
- Construct the LambdaExpression that you will pass to
Where
(i.e.,Expression.Lambda(methodCallExpression, c)
.
The fourth step will vary if you want to combine boolean expressions as indicated in your comment.
I hope that helps.
Solution 3
Note that I've added inner expression. There's a bit of closure code that you'd need to make, since you have a captured variable.
Note the simplification opportunity when using code from https://stackoverflow.com/a/3472250/90475.
Minimal code to get the 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's 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 it's far from trivial, and you need to simplify your original query.
I would also try running LinqPad for quick prototyping
Solution 4
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);
Then you can use filter
as a parameter in any IQueryable<Customer>.Where
method.
Pit Ming
Updated on June 09, 2022Comments
-
Pit Ming almost 2 years
I want to dynamically generate a linq.expressions.expression statement which I can use as a filter.
Here is a sample Linq query that I'd like to convert to an Expression:
ctx.customer.where(c=>ctx.Invoice.Any(i=>i.customerId == c.id));
Here's 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 assist.