我正在尝试创建一个动态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运算符。 有谁知道如何做到这一点?
谢谢。
答案 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"
的末尾。但要小心注射。在这里,您需要使用参数。