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

10,003

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

  1. Retrieve the MethodInfo for the correct overload of the generic Enumerable.Any method.
  2. Construct the non-generic MethodInfo from the generic one (i.e., call MakeGenericMethod).
  3. Construct the PropertyExpression that you will pass as the first argument to the method (representing ctx.Invoce)
  4. 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).
  5. Construct the LambdaExpression (e.g., var innerLambda = Expression.Lambda(rightPart, i);)
  6. Construct the MethodCallExpression representing the call to the method, passing the MethodInfo and innerLambda.
  7. 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.

Share:
10,003
Pit Ming
Author by

Pit Ming

Updated on June 09, 2022

Comments

  • Pit Ming
    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.