Вложение ИЛИ с использованием Linq PredicateBuilder - PullRequest
2 голосов
/ 08 февраля 2012

Я использую построитель предикатов для написания следующего кода:

IEnumerable<int> ids= new List<int> { 47, 48 };

var predicate = PredicateBuilder.False<Customer>();

predicate = predicate.And(x => x.CreatedAt >= fromDate && x.CreatedAt <= toDate);

foreach (var id in ids)
{
    predicate = predicate.Or(x => x.Source.Id == id);
}

var result = Database.Set<Customer>().AsExpandable()
                                     .Where(predicate)
                                     .ToList();

Сгенерированный SQL выглядит так (просто предложение WHERE):

WHERE ([Filter6].[SourceId] IN (@p__linq__0,@p__linq__1))
AND ([Filter6].[CreatedAt] >= @p__linq__2)
AND ([Filter6].[CreatedAt] <= @p__linq__3)',
N'@p__linq__0 int,
@p__linq__1 int,
@p__linq__2 datetime2(7),
@p__linq__3 datetime2(7)',
@p__linq__0=48,
@p__linq__1=48,
@p__linq__2='2012-02-07 21:59:55.0437985',
@p__linq__3='2012-02-07 22:04:55.5748288'

Похоже, что идентификатор 48 получилназначается дважды в SQL.Не уверен почему?

Ответы [ 2 ]

6 голосов
/ 08 февраля 2012
foreach (var id in ids)
{
    predicate = predicate.Or(x => x.Source.Id == id);
}

Вы закрываете переменную цикла.Вместо этого сделайте локальную копию вашей переменной id:

foreach (var id in ids)
{
    int localId = id;
    predicate = predicate.Or(x => x.Source.Id == localId);
}

Поскольку Linq ленив, ваш предикат Or и, следовательно, id будут оцениваться только при выполнении запроса и в то время значение из id является последним элементом в коллекции ids.Поведение foreach в этом отношении будет изменено в C # 5, где это больше не будет проблемой.Для получения дополнительной информации прочитайте «Закрытие переменной цикла, считающейся вредной»

4 голосов
/ 08 февраля 2012

Если это все, что вы делаете, и ваш список не длинный, вам вообще не нужен построитель предикатов.

 var result = Database.Set<Customer>().AsExpandable() 
                                      .Where( x => x.CreatedAt >= fromDate
                                                   && x.CreatedAt <= toDate
                                                   && ids.Contains( x.Source.Id ) ) 
                                      .ToList();

Если вы собираетесь использовать построитель предикатов, тогда вам нужнополностью построить предложение ИЛИ, а затем И все это за один раз.Кроме того, вам нужно использовать локальную переменную id, иначе она закроется над переменной итерации и получит привязку только к последнему значению.

// This should translate to True AND (u AND x) AND (FALSE OR y OR z)
var predicate = PredicateBuilder.True<Customer>();

predicate = predicate.And(x => x.CreatedAt >= fromDate && x.CreatedAt <= toDate);      

var idPredicate = PredicateBuilder.False<Customer>();      
foreach (var id in ids)      
{   
    var localId = id;   
    idPredicate = idPredicate.Or(x => x.Source.Id == localId);      
}

predicate = predicate.And( idPredicate );
...