这是我的情况:
有一组对象,其中每个对象都包含一个Dictionary<string, string>
。用户可以通过选择Key
中的Dictionary
(一种>
或CONTAINS
这样的运算符,来从另一个应用程序为此集合构建一组查询,以获取一个子集,等等,还有Value
。他们还可以平衡括号以创建查询组,并选择AND/OR
运算符来组合查询。
例如,假设我有一个Car
对象的集合,并且Dictionary
包含Make
,Model
和Year
的键。 / p>
我的应用正在以这样的字符串形式获取这些查询:
"((Make = Honda) AND (Model CONTAINS Civic)) || (Year >= 2015)"
这告诉我,从Car
个对象的集合中,我想要具有Dictionary
和<Make, Honda>
或<Model, anything that contains "Civic">
的{{1}}键/值的汽车< / p>
因此,我将它们解析出并放入<Year, greater than or equal to 2015>
中,其中包含QueryClass
,Key
和Operator
的三个字符串字段。我还跟踪查询之间的运算符,以及查询是否在括号中。
当前,我必须逐个执行每个Value
来执行查询,检查先前的运算符是什么,如果它是组的一部分,等等,然后一遍又一遍地合并集合直到结束。这很乏味,而且似乎是做事情的糟糕方法。如果可以在此集合上动态构建这些LINQ查询或执行SQL语句(这些是必不可少的),那就更好了。
这是我的查询类,将解析的字符串存储在以下位置:
QueryClass
我的解析类很长,因此我不会发布整个内容,但是它返回一个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
的示例:
然后,我仅遍历每个元素,确定值是双精度还是字符串,然后对我的集合执行LINQ语句。我正在检查运算符是“ AND”还是“ OR”(如果只是一个查询,则为无),是否是组的一部分,并适当地组合结果。
答案 0 :(得分:2)
这是我将查询转换为Func
的实现。由于我不确定您的收藏夹中的类型是什么,所以我制作了一个接口来表示具有attributes
Dictionary<string, string>
的对象并对其进行处理。
基本上,我向QueryClass
添加了一种方法,以将其转换为Expression
。它使用一个辅助字典字符串-> lambda来为每个运算符建立适当的比较Expression
。
然后,我添加了一个类,将List<object>
转换为适合LINQ Func<IItem,bool>
过滤器的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
的集合cs
,您可以执行以下操作:
var ans = cs.Where(LinqBuilder.BuildLINQ(q));
答案 1 :(得分:1)
您应该能够使用Linq表达式(System.Linq.Expressions
)并利用谓词来处理过滤。
public IQueryable<Car> GetCars(Expression<Func<Car, bool>> filter)
{
return context.Cars.Where(filter);
}
也就是说,挑战将是基于自定义QueryClass
对象构建谓词表达式。要处理每个Dictionary上的过滤器,您可以创建一个处理每个Dictionary的方法:
public Expression<Func<Car, bool>> GetModelFilter(QueryClass modelQuery)
{
return modelQuery.Operator == "CONTAINS"? car => car.Model.Contains(modelQuery.Value) : car => car.Model == modelQuery.Value;
}
考虑到您使用的过滤器数量有限,上述情况可能是可以接受的。但是,在处理大型集合时,也可以使用反射或动态谓词生成器来更动态地完成此操作,但为简单起见,您可以遵循上述说明。
HTH
答案 2 :(得分:1)
我对这个问题的处理方法几乎没有什么不同,因为您已经拥有List<object>
,它内部包含一个QueryClass
,其中包含所有包含信息FieldName
,{{1 }}和Operator
,您会知道哪些二进制表达式必须捆绑在括号中。重要的一点是如何创建运行时表达式来处理各种情况。
以下是模拟您的情况的示例:
样品分类
Value
查询
public class Car
{
public string Make {get; set;}
public string Model {get; set;}
public int Year {get; set;}
}
Linqpad代码
((c.Make.Equals("Honda") AndAlso c.Model.Contains("Civic")) Or (c.Year >= 2015))
目的
可以看出,我手动构建了表达式,最后void Main()
{
var cars = new List<Car>();
Expression<Func<Car,bool>> e1 = c => c.Make.Equals("Honda");
Expression<Func<Car,bool>> e2 = c => c.Model.Contains("Civic");
Expression<Func<Car,bool>> e3 = c => c.Year >= 2015;
var expr1 = Expression.AndAlso(e1.Body,e2.Body);
var expr2 = e3;
var finalExpression = Expression.Or(expr1,expr2.Body);
finalExpression.Dump();
}
最终表达式,因为在Linqpad中,它提供了如何动态构建表达式的图形表示,整体图像太大且太深粘贴到此处(您可以尝试使用LinqPad进行尝试),但是以下是相关详细信息:
创建一个Dump
,它用作表示Car类对象的lambda参数(与Query类的字段无关)
ParameterExpression
创建var parameterExpression = Expression.Parameter(typeof(Car),"c");
以访问在查询中使用的Car类的每个相关字段(这需要Query类的Field属性)
MemberExpression
完成表达式:
a。)var makeMemberAccessExpression = Expression.MakeMemberAccess(parameterExpression, typeof(Car).GetProperty("Make"));
var modelMemberAccessExpression = Expression.MakeMemberAccess(parameterExpression, typeof(Car).GetProperty("Model"));
var yearMemberAccessExpression = Expression.MakeMemberAccess(parameterExpression, typeof(Car).GetProperty("Year"));
我们创建如下:(这需要c => c.Make.Equals("Honda")
的{{1}}属性)
Value
b。)QueryClass
可以表示如下,我们需要为var makeConstantExpression = Expression.Constant("Honda");
var makeEqualExpression = Expression.Equal(makeMemberAccessExpression, makeConstantExpression);
方法提供MethodInfo并创建MethodCallEXpression
c.Model.Contains("Civic")
c。)string Contains
可以简单地投影为:
var modelConstantExpression = Expression.Constant("Civic");
var stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var modelContainsMethodExpression = Expression.Call(modelMemberAccessExpression, stringContainsMethodInfo, modelConstantExpression);
表达式a。)和b。)组合如下:
c.Year >= 2015
表达式c。)是独立的:
var yearConstantExpression = Expression.Constant(2015);
var yearGreaterThanEqualExpression = Expression.GreaterThanOrEqual(yearMemberAccessExpression, yearConstantExpression);
最终((c.Make.Equals("Honda") AndAlso c.Model.Contains("Civic"))
var firstExpression = Expression.AndAlso(makeEqualExpression,modelContainsMethodExpression);
并创建一个c.Year >= 2015
var secondExpression = yearGreaterThanEqualExpression;
Combined Expression
Func Delegate
委托因此可以在// Expressions combined via Or (||)
var finalCombinedExpression = Expression.Or(firstExpression,secondExpression);
// Create Lambda Expression
var lambda = Expression.Lambda<Func<Car,bool>>(finalCombinedExpression, parameterExpression);
// Create Func delegate via Compilation
var func = lambda.Compile();
中任何需要func
设计建议
Linq
这样的二进制表达式都由表达式树公开,可以直接使用。对于像Func<Car,bool>
这样的默认情况下可用的方法,您需要Reflection来获得b Equal, GreaterThan, LessThan, LessThanOrEqual,GreaterThanOrEqual
,类似地,它可以是完成了静态方法,只是有对象表达式Contains
,其中每个列表都使用MethodInfo
或List<Expression>
在括号中合并,每个列表因此可以使用AndAlso
通过这种方法,您将能够在运行时使用Linq表达式构造非常复杂的需求。