LINQ表达式树Where()内的Any() [英] LINQ Expression Tree Any() inside Where()

查看:98
本文介绍了LINQ表达式树Where()内的Any()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试生成以下LINQ查询:

I'm trying to generate the following LINQ query:

//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<AdAccountAlert>()
  .ToList()
  .GroupJoin(dataContext.AlertSubscriptions,
    a => new Tuple<int, string>(a.AdAccountId, typeof(AdAccount).Name),
    s => new Tuple<int, string>(s.EntityId, s.EntityType),
    (Alert, Subscribers) => new Tuple<AdAccountAlert, IEnumerable<AlertSubscription>> (Alert, Subscribers))
  .Where(s => s.Item2.Any())
  .ToDictionary(kvp => (Alert)kvp.Item1, kvp => kvp.Item2.Select(s => s.Username));

使用表达式树(当我需要使用反射和运行时类型时,这似乎是我可以执行此操作的唯一方法).请注意,在实际代码(请参见下文)中,AdAccountAlert实际上是通过反射和for循环动态生成的.

Using Expression Trees (which seems to be the only way I can do this when I need to use reflection and run-time types). Note that in the real code (see below) the AdAccountAlert is actually dynamic through reflection and a for-loop.

我的问题:我可以生成直到.Where()子句之前的所有内容.由于类型不兼容,whereExpression方法调用会爆炸.通常,我知道该放在哪里,但是Any()方法调用使我感到困惑.我尝试了所有我能想到的类型,但没有运气. .Where()和.ToDictionary()的任何帮助将不胜感激.

My problem: I can generate everything up to the .Where() clause. The whereExpression method call blows up because of incompatible types. Normally I know what to put there, but the Any() method call has me confused. I've tried every type I can think of and no luck. Any help with both the .Where() and .ToDictionary() would be appreciated.

这是我到目前为止所拥有的:

Here's what I have so far:

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<Alert, IEnumerable<string>>();

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

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

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

  //Generates: .OfType<alertType>()
  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, IEnumerable<AlertSubscription>>(Alert, Subscribers)
  var joinResultType = doubleTuple.MakeGenericType(new Type[] { alertType, typeof(IEnumerable<AlertSubscription>) });
  ParameterExpression alertTupleParameter = Expression.Parameter(alertType, "Alert");
  ParameterExpression subscribersTupleParameter = Expression.Parameter(typeof(IEnumerable<AlertSubscription>), "Subscribers");
  NewExpression joinResultObject = Expression.New(
    joinResultType.GetConstructor(new Type[] { alertType, typeof(IEnumerable<AlertSubscription>) }),
    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, IEnumerable<AlertSubscription>>(Alert, Subscribers))
  IQueryable<AlertSubscription> 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);

推荐答案

请注意:ToList()之后的所有内容(包括c2>)都不能在IQueryable<T>上使用,而只能在IEnumerable<T>上使用.因此,无需创建表达式树.毫无疑问,EF或类似机构对此没有任何解释.

Please note: Everything after and including ToList() won't work on IQueryable<T> but on IEnumerable<T>. Because of this, there is no need to create expression trees. It certainly is nothing that is interpreted by EF or similar.

如果您查看由编译器为原始查询生成的代码,您会看到它仅在第一次调用ToList之前生成表达式树.

If you would look at the code that is generated by the compiler for your original query, you would see that it generates expression trees only until just before the first call to ToList.

示例:

以下代码:

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

由编译器翻译为:

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

请注意,它如何为ToList之前的所有内容生成表达式.扩展之后的所有内容都只是对扩展方法的普通调用.

Please note how it generates expressions for everything up to ToList. Everything after and including it are simply normal calls to extension methods.

如果您在代码中没有模仿它,您实际上会向LINQ提供程序发送对Enumerable.ToList的调用-然后它会尝试将其转换为SQL并失败.

If you don't mimick this in your code, you will actually send a call to Enumerable.ToList to the LINQ provider - which it then tries to convert to SQL and fail.

这篇关于LINQ表达式树Where()内的Any()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆