如何创建一个包含Any()调用的System.Linq.Expressions.Expression对象

时间:2012-03-27 14:43:20

标签: c# linq lambda linq-expressions

我想动态生成一个linq.expressions.expression语句,我可以将其用作过滤器。

以下是我要转换为表达式的Linq查询示例:

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

这是我的尝试

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")

请协助。

4 个答案:

答案 0 :(得分:7)

当我需要手动创建linq表达式时,我只是让.Net为我创建来自lambda的表达式然后我可以探索它的结构。例如,在debug

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

并检查表达式变量。

答案 1 :(得分:5)

我认为LinqExpression来自using LinqExpression = System.Linq.Expressions.Expression;

Any没有特定的表达类型,因为它不是运算符等。 Any是一个静态方法,因此您应该为此创建一个Call表达式。

您需要基于静态方法语法而不是扩展方法语法来构建表达式:

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

这有很多步骤。这是大纲,因为我没有时间正确地完成所有步骤。我不完全确定如何表示内部lambda中使用的c参数不是该lambda的参数,而是外部lambda的参数。概括地说,你需要

  1. 检索MethodInfo以获取通用Enumerable.Any方法的正确重载。
  2. 从通用方法构造非泛型MethodInfo(即调用MakeGenericMethod)。
  3. 构造您将作为方法的第一个参数传递的PropertyExpression(代表ctx.Invoce
  4. 构造将作为方法的第二个参数传递的lambda表达式的主体(即示例代码中的rightPart)。
  5. 构造LambdaExpression(例如,var innerLambda = Expression.Lambda(rightPart, i);
  6. 构造MethodCallExpression,表示对方法的调用,传递MethodInfo和innerLambda。
  7. 构建您将传递给Where的LambdaExpression(即Expression.Lambda(methodCallExpression, c)
  8. 如果您想要结合评论中指明的布尔表达式,第四步将会有所不同。

    我希望有所帮助。

答案 2 :(得分:2)

请注意,我添加了内部表达式。你需要做一些关闭代码,因为你有一个捕获的变量。

请注意使用https://stackoverflow.com/a/3472250/90475中的代码时的简化机会。

在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));
      }
   }
}

这是我在反射器中看到的内容:

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 });
}

足够接近政府工作......这告诉我,这远非微不足道,您需要简化原始查询。

我也会尝试运行LinqPad进行快速原型设计

答案 3 :(得分:1)

添加此代码:

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);

然后,您可以在filter方法中使用IQueryable<Customer>.Where作为参数。