Как сделать объект System.Linq.Expressions.Expression, содержащий вызов Any () - PullRequest
4 голосов
/ 27 марта 2012

Я хочу динамически генерировать оператор 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 ]

7 голосов
/ 27 марта 2012

Когда мне нужно вручную создать выражение linq, я просто заставляю .Net создавать для меня такое выражение из лямбды, а затем я могу исследовать его структуру. Например запустить под отладкой

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

и проверьте переменную выражения.

5 голосов
/ 27 марта 2012

Я предполагаю, что LinqExpression взято из using LinqExpression = System.Linq.Expressions.Expression;.

Не существует определенного типа выражения для Any, потому что это не оператор или тому подобное.Any является статическим методом, поэтому для этого следует создать выражение Call.

Вам нужно будет построить выражение на основе синтаксиса статического метода, а не синтаксиса метода расширения:

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

Есть много шагов к этому.Вот он в общих чертах, потому что у меня нет времени, чтобы правильно выполнить все шаги.Я не совсем уверен, как представить тот факт, что параметр c, используемый во внутренней лямбде, является не параметром этой лямбды, а скорее внешней лямбды.В общих чертах вам необходимо

  1. Извлечь MethodInfo для правильной перегрузки универсального метода Enumerable.Any.
  2. Построить неуниверсальный MethodInfo из универсального метода (т. Е., call MakeGenericMethod).
  3. Создайте PropertyExpression, который вы передадите в качестве первого аргумента метода (представляющий ctx.Invoce)
  4. Создайте тело лямбда-выражения, которое вы передадитев качестве второго аргумента метода (т. е. rightPart в коде вашего примера).
  5. Создайте выражение LambdaExpression (например, var innerLambda = Expression.Lambda(rightPart, i);)
  6. Создайте выражение MethodCallExpression, представляющее вызов метода, передавая MethodInfo и innerLambda.
  7. Создайте выражение Lambda, которое вы передадите в Where (т. е. Expression.Lambda(methodCallExpression, c).

Четвертый шаг будет отличаться, если вы хотите объединитьлогические выражения, указанные в вашем комментарии.

Надеюсь, это поможет.

2 голосов
/ 27 марта 2012

Обратите внимание, что я добавил внутреннее выражение. Вам нужно сделать немного кода закрытия, поскольку у вас есть захваченная переменная.

Обратите внимание на возможность упрощения при использовании кода из 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 для быстрого прототипирования

1 голос
/ 27 марта 2012

Добавьте этот код:

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.

...