c# - 从字符串动态构建 LINQ 查询
问题描述
这是我的场景:
有一个对象集合,其中每个对象都包含一个Dictionary<string, string>
. 用户可以从另一个应用程序中为此集合构建一组查询,以通过选择Key
中的a Dictionary
、诸如>
orCONTAINS
等运算符和 a来获取子集Value
。他们还可以平衡括号以创建查询组并选择AND/OR
运算符来组合查询。
例如,假设我有一个Car
对象集合和Dictionary
包含Make
、Model
和的键Year
。
我的应用程序以字符串的形式获取这些查询,如下所示:
"((Make = Honda) AND (Model CONTAINS Civic)) || (Year >= 2015)"
这告诉我,从我想要具有和OR键/值的Car
汽车的对象集合中Dictionary
<Make, Honda>
<Model, anything that contains "Civic">
<Year, greater than or equal to 2015>
因此,我将它们解析出来,并将它们放入一个QueryClass
包含 、 和 的三个字符串Key
字段Operator
中Value
。我还跟踪查询之间的运算符,以及它们是否在一组括号中。
目前,我必须QueryClass
逐个执行查询,检查前一个运算符是什么,它是否是组的一部分等,并一遍又一遍地组合集合,直到它到达末尾。这很乏味,似乎是一种糟糕的做事方式。如果有办法在这个集合上动态构建这些 LINQ 查询或执行 SQL 语句(这些是必不可少的),那就更好了。
这是我存储解析字符串的查询类:
class QueryClass
{
public string FieldName { get; set; }
public string Operator { get; set; }
public object Value { get; set; }
public QueryClass(string pInput)
{
var returned = RegexHelpers.SplitKeyValue(pInput); //just returns a string like "Make = Honda" into three parts
if (returned != null)
{
FieldName = returned.Item1;
Operator = returned.Item2;
Value = returned.Item3;
}
}
}
我的解析类很长,所以我不会发布整个内容,但它会返回List<object>
每个元素所在的位置:
- 一个
QueryClass
- “与”或“或”
- 另一个列表,这意味着它是一组按括号分组的查询,包含上面的两个选项。
List<object>
这是我在解析字符串后得到的示例:
然后,我只需遍历每个元素,确定该值是双精度值还是字符串,然后对我的集合执行 LINQ 语句。我正在检查运算符是“AND”还是“OR”(如果它只是一个查询,则没有),它是否是组的一部分,并适当地组合结果。
解决方案
这是我将您的查询转换为Func
. 由于我不确定您的集合中是什么类型,所以我制作了一个接口来表示具有attributes
Dictionary<string, string>
并处理它的对象。
基本上我添加了一种方法来QueryClass
将其转换为Expression
. 它使用帮助字典 string->lambdaExpression
为每个运算符构建适当的比较。然后我添加了一个类来将其List<object>
转换为Func<IItem,bool>
适合 LINQ 的Where
过滤器。
public interface IItem {
Dictionary<string, string> attributes { get; set; }
}
class QueryClass {
public string FieldName { get; set; }
public string Operator { get; set; }
public object Value { get; set; }
public QueryClass(string pInput) {
var returned = RegexHelpers.SplitKeyValue(pInput); //just returns a string like "Make = Honda" into three parts
if (returned != null) {
FieldName = returned.Item1;
Operator = returned.Item2;
Value = returned.Item3;
}
}
static MethodInfo getItemMI = typeof(Dictionary<string, string>).GetMethod("get_Item");
static Dictionary<string, Func<Expression, Expression, Expression>> opTypes = new Dictionary<string, Func<Expression, Expression, Expression>> {
{ "==", (Expression lhs, Expression rhs) => Expression.MakeBinary(ExpressionType.Equal, lhs, rhs) },
{ ">=", (Expression lhs, Expression rhs) => Expression.MakeBinary(ExpressionType.GreaterThanOrEqual, Expression.Call(lhs, typeof(String).GetMethod("CompareTo", new[] { typeof(string) }), rhs), Expression.Constant(0)) },
{ "CONTAINS", (Expression lhs, Expression rhs) => Expression.Call(lhs, typeof(String).GetMethod("Contains"), rhs) }
};
static MemberInfo attribMI = typeof(IItem).GetMember("attributes")[0];
public Expression AsExpression(ParameterExpression p) {
var dictField = Expression.MakeMemberAccess(p, attribMI);
var lhs = Expression.Call(dictField, getItemMI, Expression.Constant(FieldName));
var rhs = Expression.Constant(Value);
if (opTypes.TryGetValue(Operator, out var exprMakerFn))
return exprMakerFn(lhs, rhs);
else
throw new InvalidExpressionException($"Unrecognized operator {Operator}");
}
}
public class LinqBuilder {
static Type TItems = typeof(IItem);
static Expression BuildOneLINQ(object term, ParameterExpression parm) {
switch (term) {
case QueryClass qc: // d => d[qc.FieldName] qc.Operator qc.Value
return qc.AsExpression(parm);
case List<object> subQuery:
return BuildLINQ(subQuery, parm);
default:
throw new Exception();
}
}
static Expression BuildLINQ(List<object> query, ParameterExpression parm) {
Expression body = null;
for (int queryIndex = 0; queryIndex < query.Count; ++queryIndex) {
var term = query[queryIndex];
switch (term) {
case string op:
var rhs = BuildOneLINQ(query[++queryIndex], parm);
var eop = (op == "AND") ? ExpressionType.AndAlso : ExpressionType.OrElse;
body = Expression.MakeBinary(eop, body, rhs);
break;
default:
body = BuildOneLINQ(term, parm);
break;
}
}
return body;
}
public static Func<IItem, bool> BuildLINQ(List<object> query) {
var parm = Expression.Parameter(TItems, "i");
return Expression.Lambda<Func<IItem, bool>>(BuildLINQ(query, parm), parm).Compile();
}
}
一旦你有了这个,你可以传入一个List<object>
表达式,然后过滤你的集合。给定一个查询q
和IItem
s的集合cs
,您可以执行以下操作:
var ans = cs.Where(LinqBuilder.BuildLINQ(q));
推荐阅读
- r - 如何用 R 绘制一个简单的矩形
- python - 将pdf转换为base64并附加到json数组中
- firefox-addon-webextensions - 匹配 urlpatterns 和属性的 webextensions
- osmnx - osmnx 返回....意外...轴承
- c++ - 在 M1 Mac 上从 C++ 链接到 curl - arm64 的未定义符号
- android - 带有 ExoPlayer 的 RecyclerView 是滞后的
- php - 如何在学说/php中的实体属性中聚合来自不同表的信息?
- docker - 如何在 Docker Compose 中启动 Apache Drill
- c++ - 无法从本地 Windows 调试器切换到本地机器(VS 2019)
- swiftui - 如何在 SwiftUI 中添加编号/项目符号列表?