2013-05-29 59 views
7

我试图生成以下LINQ查询: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<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循环动态变化的。

我的问题:我可以生成所有的.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<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); 
+19

只有一个问题:您认为您编写的代码易于阅读和维护吗? – Marco

+3

真实的代码被分解成单独的函数,使它更容易阅读。我将所有内容放在一起。 如果您在询问我如何使用动态构建表达式树,正如我在帖子中所述,这是迄今为止我发现的最佳选择。 PredicateBuilder不做这项工作,DynamicLinq库也没有。 – user1924929

+0

这一切都很好,我只是想知道,因为你把所有的东西放在上下文中;我明白你的意思。 – Marco

回答

3

请注意:一切后,包括ToList()不会在IQueryable<T>IEnumerable<T>工作。因此,不需要创建表达式树。这当然不是EF或类似的解释。

如果您要查看编译器为您的原始查询生成的代码,您会发现它仅在第一次调用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。包括它在内的所有内容都只是对扩展方法的普通调用。

如果你没有在你的代码中模仿这个,你实际上会发送一个到Enumerable.ToList的调用给LINQ提供者 - 然后它尝试转换为SQL并失败。

+0

_“因此,不需要创建表达式树。”_ =>除了他想写的查询依赖于在编译时不知道的类型,所以是的,他需要动态构建它。 –

+0

那么为什么ToList()? – Stu

+1

@MikeStrobel如果你的意思是OP需要在'ToList'之后动态构造东西,那么当然,但这并不需要,也许不应该使用表达式树来完成。在不需要表达式树的情况下,大大简化了问题。 – hvd

0

看起来,在构建whereLambda时,您的第二个参数应该是subscribersParameter而不是subscriptionParameter。至少,这将是你的例外的原因。