Динамический список условий LinQ Query - PullRequest
1 голос
/ 14 апреля 2019

Я пытаюсь создать метод динамического запроса LINQ, который получает List > для применения условий.

В основном, для каждого элемента списка , Я хочу добавить условие в свой запрос.

Этот проект разрабатывается с использованием шаблона репозитория, который очень популярен в .NET Core.

В SQL яудалось получить правильный запрос:

SELECT * FROM events e
WHERE (e.store = "CO" && e.brand = "YL") || (e.store = "VA" && e.brand = "CD");

Элементы List<KeyValuePair<string, string>> выглядят так:

List<KeyValuePair<string, string>> filters = new List<KeyValuePair<string, string>>()
{
  new KeyValuePair<string, string>("CO","YL"),
  new KeyValuePair<string, string>("VA", "CD")
};

Теперь мне нужен метод, который проходит через мой List<KeyValuePair<string, string>> и строитдинамический запрос LINQ для каждого элемента списка.Если у меня есть список с 5 элементами, я хочу запрос с 5 условиями с ИЛИ между ними, например:

SELECT * FROM events e
WHERE 
(e.store = "CO" && e.brand = "YL") ||
(e.store = "VA" && e.brand = "CD") ||
(e.store = "FP" && e.brand = "CH") ||
(e.store = "MC" && e.brand = "AR") ||
(e.store = "AB" && e.brand = "CH");

Это моя попытка:

var query = Query();
foreach (var item in filters)
{
 query = query.Where(e => e.Store.Equals(i.Key) && e.Brand.Equals(i.Value));
}
var results = await query.ToListAsync(ct);

Но так яне может применить оператор ИЛИ между условиями.Кто-нибудь знает, как это можно сделать?

Спасибо.

Ответы [ 3 ]

2 голосов
/ 15 апреля 2019

Похоже, что вам проще всего было бы использовать Dynamic Linq , поскольку это позволяет вам создавать ваши "(A && B) || (C && D)" как строки во время выполнения и использовать их впункт Где.В качестве альтернативы вы можете построить деревья выражений;см. например /8288679/kak-postroit-lyambda-vyrazhenie-dereva-s-neskolkimi-usloviyami

0 голосов
/ 15 апреля 2019

Этот вопрос действительно интересовал меня, и я попытался скомпилировать что-то работающее без использования сторонних библиотек.Далее я представлю фрагменты кода, которые вместе дали желаемый результат.Но, скорее всего, это все в образовательных целях.

Во-первых, нам нужны функция и класс, взятые отсюда: https://www.c -sharpcorner.com / UploadFile / c42694 / dynamic-query-in-linq-используя-предикат-строитель /

static Expression<T> Compose<T>(Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
{
    // zip parameters (map from parameters of second to parameters of first)    
    var map = first.Parameters
        .Select((f, i) => new { f, s = second.Parameters[i] })
        .ToDictionary(p => p.s, p => p.f);

    // replace parameters in the second lambda expression with the parameters in the first    
    var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

    // create a merged lambda expression with parameters from the first expression    
    return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}

class ParameterRebinder : ExpressionVisitor {
    readonly Dictionary<ParameterExpression, ParameterExpression> map;

    ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
    {
        this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
    }

    public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
    {
        return new ParameterRebinder(map).Visit(exp);
    }

    protected override Expression VisitParameter(ParameterExpression p)
    {
        ParameterExpression replacement;

        if (map.TryGetValue(p, out replacement))
        {
            p = replacement;
        }

        return base.VisitParameter(p);
    }
}

Тогда мы можем сделать что-то вроде этого (я использовал кортежи, потому что они больше соответствуют идее):

var tup = new List<Tuple<string, string>> { 
    new Tuple<string, string>("CO", "YL"),
    new Tuple<string, string>("VA", "CD") 
};

Expression<Func<YOUR_TYPE_HERE, bool>> baseFunc = t => false;

foreach (var a in tup)
{
    Expression<Func<YOUR_TYPE_HERE, bool>> addFunc = t => t.store == a.Item1 && t.brand == a.Item2;
    baseFunc = Compose(baseFunc, addFunc, Expression.OrElse);
}


var res = _context.YOUR_ENTITY_NAME.Where(baseFunc).ToList();

Iпроверил, что он выполняется в одном запросе и оценил на стороне базы данных.

UPD: Если вы хотите повысить производительность и хотите использовать бит sql:

var res = context.YOUR_ENTITY_NAME.FromSql("SELECT * FROM YOUR_ENTITY_NAME WHERE (...and...) or (...and...)").ToList();

Вы можете сгенерировать часть "где" вручную и поместить ее как строку в конце на "SELECT * FROM YOUR_ENTITY_NAME WHERE".Но будьте осторожны с уколами.Здесь вам нужно использовать параметры.

0 голосов
/ 14 апреля 2019

Я предполагаю, что в вашем коде есть ошибка.e.Store.Equals(i.Key) должно быть e.Store.Equals(item.Key).Поправьте меня, если я ошибаюсь.

Это должно сделать работу.

query = query.Where(e => filters.Any(f => e.Store.Equals(filter.Key) && e.Brand.Equals(filter.Value)));

Обратите внимание, что этот запрос может быть выполнен на клиенте, а не в базе данных.Смотрите здесь: https://docs.microsoft.com/en-us/ef/core/querying/client-eval

Это означает, что запрос будет работать до EF Core 2.2, но не с EF Core 3.0.Смотрите последние изменения в EF Core 3.0 здесь: https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes

...