4

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.

marc_s
  • 675,133
  • 158
  • 1,253
  • 1,388
Pit Ming
  • 389
  • 4
  • 13

4 Answers4

8

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.

Nikolay
  • 3,354
  • 1
  • 24
  • 25
5

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.

phoog
  • 39,474
  • 5
  • 73
  • 110
2

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

Community
  • 1
  • 1
GregC
  • 7,517
  • 2
  • 48
  • 63
  • Maybee it's totally obvious to you, but not for me. What ere those 2 lambda, how can my context be a constant and how do I combine the all together +? – Pit Ming Mar 27 '12 at 15:22
  • 1
    The original query seems not to be so complex... I think I'll have to build view so i can generate something simpler... Too bad – Pit Ming Mar 27 '12 at 15:38
  • Building the lambda and using Reflector is a good way of working on this. But obviously I can't use the output... I'll get home very sad today... – Pit Ming Mar 27 '12 at 15:44
  • @PitMing I took the liberty of simplifying your query somewhat. – GregC Mar 27 '12 at 16:00
1

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.

Balazs Tihanyi
  • 6,159
  • 4
  • 20
  • 23