Преобразуйте LINQ в обычный Foreach

Я нашел ниже кусок кода в одном из наших проектов. Я застрял на два дня:-(пытаюсь понять Aggregate & LinQKit Expand.

Можете ли вы помочь в преобразовании ниже LINQ для нормальной работы foreach?

public Expression<Func<Purchase, bool>> forTarget(List<string> idList)
{
    Expression<Func<Purchase, string>> fc = p => p.ClientId;
    Expression<Func<Purchase, bool>> predicate = m => false;

    return idList.Aggregate(predicate, (p, id) => p.Or(m => fc.Invoke(m) == id), p => p.Expand());
}

internal class Purchase
{
    public int Price { get; set; }
    public string Description { get; set; }
    public string ClientId { get; set; }
}

public class Client
{
    public string Id { get; set; }
}   

Или, по крайней мере, любой указатель на то, что это выражение LINQ делает в списке, был бы очень полезен.

return idList.Aggregate(predicate,
        (p, id) => p.Or(m => fc.Invoke(m) == id),
        p => p.Expand());

1 ответ

Решение

Функция перебирает коллекцию элементов и создает предикат, добавляя or условие для каждого ClientId стоимость имущества.

В ранних версиях Linq2SQL не было поддержки метода Contains поэтому вы не смогли выполнить такой запрос:

IEnumerable<Purchase> purchases = LoadSelectedItems();
var clientIds = purchases.Select( p => p.ClientId ).ToArray();
var results = db.Clients.Where( c => clientIds.Contains( c.Id )); // Did not work.

Обходной путь для этой проблемы должен создать предикат, который проверял бы, используя or будь то Id будет соответствовать конкретному значению. Итак, для приведенного выше примера, если clientIds = {1, 2, 3} Where пункт будет написан как:

var results = db.Clients.Where( c => c.Id == 1 || c.Id == 2 || c.Id == 3);

Как вы можете видеть, такого рода утверждение не очень элегантно, становится нечитаемым, когда набор значений для проверки (т.е. clientIds) очень велика, и, самое главное, вы не можете знать априори, какие будут значения для их жесткого кодирования. Таким образом, чтобы преодолеть эту проблему, решение состоит в том, чтобы обобщить вышеупомянутый предикат с переменной коллекцией значений. И это делается просто с помощью следующего алгоритма:

  1. Создать Expression это возвращает false; если мы вернемся true компилятор закоротит оценку (потому что мы используем or) и вернет true для всех элементов;
  2. Для каждого элемента в коллекции значений добавьте or пункт со значением элемента.

Теперь ваш пример может быть преобразован в foreach следующим образом:

// start with a predicate returning false
// this is the seed of the Aggregate method
Expression<Func<Purchase, bool>> predicate = m => false;
// Now, iterate the collection and build the full predicate
foreach( var id in idList)
{
    // Build the predicate by invoking a function which returns the client id of the 
    // purchase and comparing it with the value of the current id from the idList
    predicate = predicate.Or(item => item.ClientId == id);
}

Надеюсь это поможет.

Другие вопросы по тегам