2013-05-29 4 views
7

Я пытаюсь создать следующий запрос LINQ:LINQ Expression Tree Любой() внутри Где()

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

Использование Expression Trees (который, кажется, единственный способ, которым я могу это сделать, когда мне нужно использовать отражения и типы времени выполнения). Обратите внимание, что в реальном коде (см. Ниже) 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. По крайней мере, это послужило бы причиной вашего исключения.

Смежные вопросы