LinQ查询动态条件列表

时间:2019-04-14 19:39:33

标签: c# entity-framework linq .net-core repository-pattern

我正在尝试创建一个动态LINQ查询方法,该方法将接收List >以应用条件。

基本上,对于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个条件,且条件之间具有OR:

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

但是这样我不能在条件之间应用OR运算符。 有谁知道如何做到这一点?

谢谢。

3 个答案:

答案 0 :(得分:2)

看起来最简单的选择就是使用Dynamic Linq,因为它允许您在运行时将“(A && B)||(C && D)”创建为字符串并在Where子句中使用。另外,您可以构建表达式树。例如查看http://stackoverflow.com/questions/6295926/how-build-lambda-expression-tree-with-multiple-conditions

答案 1 :(得分:0)

我假设您的代码有误。 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

答案 2 :(得分:0)

这个问题确实让我感兴趣,因此我尝试在不使用第三方库的情况下编译出可以正常工作的东西。接下来,我将介绍一些代码,这些代码共同提供了预期的结果。但这很可能都是出于教育目的。

首先,我们需要从此处获取的函数和类:https://www.c-sharpcorner.com/UploadFile/c42694/dynamic-query-in-linq-using-predicate-builder/

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

我检查了它是否在一个查询中执行,并在数据库端进行了评估。

UPD::如果您想获得更高的性能,并想使用一点SQL:

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

您可以手动生成“ where”部分,并将其作为字符串放在"SELECT * FROM YOUR_ENTITY_NAME WHERE"的末尾。但要小心注射。在这里,您需要使用参数。