Дерево выражения LINQ Any () внутри Where ()

m пытается сгенерировать следующий запрос LINQ:

//Query the database for all AdAccountAlerts that haven't had notifications sent out
//Then get the entity (AdAccount) the alert pertains to, and find all accounts that
//are subscribing to alerts on that entity.
var x = dataContext.Alerts.Where(a => a.NotificationsSent == null)
  .OfType()
  .ToList()
  .GroupJoin(dataContext.AlertSubscriptions,
    a => new Tuple(a.AdAccountId, typeof(AdAccount).Name),
    s => new Tuple(s.EntityId, s.EntityType),
    (Alert, Subscribers) => new Tuple (Alert, Subscribers))
  .Where(s => s.Item2.Any())
  .ToDictionary(kvp => (Alert)kvp.Item1, kvp => kvp.Item2.Select(s => s.Username));

Использование деревьев выражений (что, кажется, единственный способ сделать это, когда мне нужно использовать типы отражений и времени выполнения). Обратите внимание, что в реальном коде (см. Ниже) AdAccountAlert на самом деле динамичен за счет отражения и цикла for.

Моя проблема: Я могу сгенерировать все до предложения .Where (). Вызов метода whereExpression разрывается из-за несовместимых типов. Обычно я знаю, что там делать, но вызов метода Any () меня смущает. Я'Я пробовал каждый тип, о котором только могу подумать, и не повезло. Любая помощь с обоими .Where () и .ToDictionary () будет принята с благодарностью.

Вот'Что я имею до сих пор:

var alertTypes = AppDomain.CurrentDomain.GetAssemblies()
  .Single(a => a.FullName.StartsWith("Alerts.Entities"))
  .GetTypes()
  .Where(t => typeof(Alert).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface);

var alertSubscribers = new Dictionary();

//Using tuples for joins to keep everything strongly-typed
var subscribableType = typeof(Tuple);
var doubleTuple = Type.GetType("System.Tuple`2, mscorlib", true);

foreach (var alertType in alertTypes)
{
  Type foreignKeyType = GetForeignKeyType(alertType);
  if (foreignKeyType == null)
    continue;

  IQueryable unnotifiedAlerts = dataContext.Alerts.Where(a => a.NotificationsSent == null);

  //Generates: .OfType()
  MethodCallExpression alertsOfType = Expression.Call(typeof(Enumerable).GetMethod("OfType").MakeGenericMethod(alertType), unnotifiedAlerts.Expression);

  //Generates: .ToList(), which is required for joins on Tuples
  MethodCallExpression unnotifiedAlertsList = Expression.Call(typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(alertType), alertsOfType);

  //Generates: a => new { a.{EntityId}, EntityType = typeof(AdAccount).Name }
  ParameterExpression alertParameter = Expression.Parameter(alertType, "a");
  MemberExpression adAccountId = Expression.Property(alertParameter, alertType.GetProperty(alertType.GetForeignKeyId()));
  NewExpression outerJoinObject = Expression.New(subscribableType.GetConstructor(new Type[] { typeof(int), typeof(string)}), adAccountId, Expression.Constant(foreignKeyType.Name));
  LambdaExpression outerSelector = Expression.Lambda(outerJoinObject, alertParameter);

  //Generates: s => new { s.EntityId, s.EntityType }
  Type alertSubscriptionType = typeof(AlertSubscription);
  ParameterExpression subscriptionParameter = Expression.Parameter(alertSubscriptionType, "s");
  MemberExpression entityId = Expression.Property(subscriptionParameter, alertSubscriptionType.GetProperty("EntityId"));
  MemberExpression entityType = Expression.Property(subscriptionParameter, alertSubscriptionType.GetProperty("EntityType"));
  NewExpression innerJoinObject = Expression.New(subscribableType.GetConstructor(new Type[] { typeof(int), typeof(string) }), entityId, entityType);
  LambdaExpression innerSelector = Expression.Lambda(innerJoinObject, subscriptionParameter);

  //Generates: (Alert, Subscribers) => new Tuple(Alert, Subscribers)
  var joinResultType = doubleTuple.MakeGenericType(new Type[] { alertType, typeof(IEnumerable) });
  ParameterExpression alertTupleParameter = Expression.Parameter(alertType, "Alert");
  ParameterExpression subscribersTupleParameter = Expression.Parameter(typeof(IEnumerable), "Subscribers");
  NewExpression joinResultObject = Expression.New(
    joinResultType.GetConstructor(new Type[] { alertType, typeof(IEnumerable) }),
    alertTupleParameter,
    subscribersTupleParameter);

  LambdaExpression resultsSelector = Expression.Lambda(joinResultObject, alertTupleParameter, subscribersTupleParameter);

  //Generates:
  //  .GroupJoin(dataContext.AlertSubscriptions,
  //    a => new { a.AdAccountId, typeof(AdAccount).Name },
  //    s => new { s.EntityId, s.EntityType },
  //    (Alert, Subscribers) => new Tuple(Alert, Subscribers))
  IQueryable alertSubscriptions = dataContext.AlertSubscriptions.AsQueryable();
  MethodCallExpression joinExpression = Expression.Call(typeof(Enumerable),
    "GroupJoin",
    new Type[]
    {
      alertType,
      alertSubscriptions.ElementType,
      outerSelector.Body.Type,
      resultsSelector.ReturnType
    },
    unnotifiedAlertsList,
    alertSubscriptions.Expression,
    outerSelector,
    innerSelector,
    resultsSelector);

  //Generates: .Where(s => s.Item2.Any())
  ParameterExpression subscribersParameter = Expression.Parameter(resultsSelector.ReturnType, "s");
  MemberExpression tupleSubscribers = Expression.Property(subscribersParameter, resultsSelector.ReturnType.GetProperty("Item2"));
  MethodCallExpression hasSubscribers = Expression.Call(typeof(Enumerable),
    "Any",
    new Type[] { alertSubscriptions.ElementType },
    tupleSubscribers);
  LambdaExpression whereLambda = Expression.Lambda(hasSubscribers, subscriptionParameter);
  MethodCallExpression whereExpression = Expression.Call(typeof(Enumerable),
    "Where",
    new Type[] { joinResultType },
    joinExpression,
    whereLambda);
 Bobson29 мая 2013 г., 21:47
Я могу'Не имеет никакого смысла из того, что изначально пытается сделать оригинальный LINQ, не говоря уже о динамически генерируемом LINQ.
 Mark Hurd30 мая 2013 г., 04:41
У меня нетt рассмотрел задействованные API, ноWhere ожидает иAny возвращаетBooleanи я неэтот тип не упоминается нигде.
 usr04 июн. 2013 г., 12:54
Вы должны сообщить нам, где произошла ошибка, точное сообщение и дополнительную информацию о значениях времени выполнения всех задействованных выражений. Прямо сейчас это море кода. Трудно найти какие-либо проблемы с этим.Можете ли вы сжать его до 10 строк репро ?!
 Mark Hurd30 мая 2013 г., 04:59
Кроме того, если вы можете подтвердить ваш сгенерированный запрос в порядке без.Where оговорка (за исключением того, что.Select в.ToDictionary иногда вызов ничего не возвращает), вы должны иметь возможность просто подтвердить код, необходимый для создания эквивалентаEnumerable.Range(0,2).Select(i=>Enumerable.Range(0,i)).Where(r=>r.Any()), а затем "просто" расширение такое, что.Where пункт становится.r.item2.Any()
 MeTitus29 мая 2013 г., 21:40
Все в порядке, мне просто интересно, потому что вы помещаете все в контекст; Я понимаю что ты имеешь в виду.
 MeTitus29 мая 2013 г., 21:29
Только один вопрос: вы думаете, что код, который выВы легко читаете и поддерживаете?
 svick29 мая 2013 г., 21:50
В чем именно проблема с вашим текущим кодом? Как это терпит неудачу? Вы пытались сравнить дерево выражений, которое вы генерируете, и дерево, генерируемое C # из лямбды?
 Gert Arnold29 мая 2013 г., 22:33
Дон»не хочу bash, но почему это полезно? Я имею в виду, что исходный запрос настолько специфичен, что потребовалось бы множество спецификаций, чтобы направить построитель выражений к тому же результату. Почти как написание самого запроса. Примечание: если вы нет использоватьTuple, но анонимные типы можно обойтись безToList пока только что.ToDictionary
 user192492929 мая 2013 г., 22:18
Мы обновили код с некоторыми комментариями вверху относительно того, что пытается сделать оригинальный запрос LINQ. Что касается моей проблемы, все прекрасно работает до оператора Where (), где он взрывается, потому что я 'Я не использую правильный тип, который я считаю из-за моего вызова метода Any (). Да я'Я пытался сравнить мое дерево выражений с лямбда-выражением C #. Все выглядит правильно, но это не такМне нравится тип в моем объявлении whereExpression.
 Rune FS04 июн. 2013 г., 12:55
переменные анонимных типов строго типизированы как любая другая переменная. Тип анонимный, поэтому вы нене знаю, как это называется, но этотам точно так же
 user192492929 мая 2013 г., 22:49
"оригинал» Запрос специфичен только для того, чтобы кто-нибудь смотрел на этот контекст. Согласно моему первоначальному вопросу, реальный код перебирает все классы, которые реализуют интерфейс (IAlert), и пытается построить "оригинал» запрос для каждого типа оповещения. Я понимаю, что могу использовать анонимные типы, но этоплохой взлом, который неЯ не даю тебе строгой печати. Мне было сложнее построить деревья выражений таким образом.
 user192492929 мая 2013 г., 21:37
Настоящий код разбит на отдельные функции, что облегчает его чтение. Я собрал все здесь для контекста. Если ты'Вы спрашиваете о моем использовании динамического построения деревьев выражений, как я уже говорил в посте,я был лучшим вариантоммы нашли до сих пор. PredicateBuilder не делаетне сделал работу, как и библиотека DynamicLinq.

Ответы на вопрос(2)

Пожалуйста, обратите внимание: все после и в том числеToList() победил'работатьIQueryable но наIEnumerable, Из-за этого нет необходимости создавать деревья выражений. Это, конечно, ничто, что интерпретируется EF или подобным.

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

Пример:

Следующий код:

var query = new List().AsQueryable();
query.Where(x => x > 0).ToList().FirstOrDefault(x => x > 10);

Переводится компилятором так:

IQueryable query = new List().AsQueryable();
IQueryable arg_4D_0 = query;
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "x");
arg_4D_0.Where(Expression.Lambda(Expression.GreaterThan(parameterExpression, Expression.Constant(0, typeof(int))), new ParameterExpression[]
{
    parameterExpression
})).ToList().FirstOrDefault((int x) => x > 10);

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

Если вы неИмитируйте это в своем коде, вы фактически отправите вызовEnumerable.ToList поставщику LINQ, который затем пытается преобразовать в SQL и потерпит неудачу ".

 Stu21 июн. 2013 г., 18:18
Тогда почему ToList ()?
 Mike Strobel22 июн. 2013 г., 21:22
Если типы и свойства неНе знаю во время компиляции, что он должен делать вместо этого?
 Mike Strobel21 июн. 2013 г., 17:07
Из-за этого нет необходимости создавать деревья выражений. "=> За исключением того, что запрос, который он хочет написать, зависит от типов, которые не известны во время компиляции, поэтому да, он должен построить его динамически.
 user74338222 июн. 2013 г., 18:11
@MikeStrobel Если вы имеете в виду, что ОП необходимо динамически создавать вещи послеToListто конечно, но это нене должно быть и, вероятно, не должносделать это с помощью деревьев выражений. И опуская деревья выражений там, где их нетЭто очень сильно упрощает вопрос.

Похоже, при построенииwhereLambdaВаш второй параметр должен бытьsubscribersParameter и неsubscriptionParameter, По крайней мере, это будет причиной вашего исключения.

Ваш ответ на вопрос