从字符串

时间:2019-08-14 17:06:23

标签: c# linq subset

这是我的情况:

有一组对象,其中每个对象都包含一个Dictionary<string, string>。用户可以通过选择Key中的Dictionary(一种>CONTAINS这样的运算符,来从另一个应用程序为此集合构建一组查询,以获取一个子集,等等,还有Value。他们还可以平衡括号以创建查询组,并选择AND/OR运算符来组合查询。

例如,假设我有一个Car对象的集合,并且Dictionary包含MakeModelYear的键。 / 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>中,其中包含QueryClassKeyOperator的三个字符串字段。我还跟踪查询之间的运算符,以及查询是否在括号中。

当前,我必须逐个执行每个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>
  • “ AND”或“ OR”
  • 另一个列表,这意味着它是由括号分组的一组查询,包含上面的两个选择。

下面是一个解析字符串后得到的QueryClass的示例:

enter image description here

然后,我仅遍历每个元素,确定值是双精度还是字符串,然后对我的集合执行LINQ语句。我正在检查运算符是“ AND”还是“ OR”(如果只是一个查询,则为无),是否是组的一部分,并适当地组合结果。

3 个答案:

答案 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>表达式,然后过滤您的集合。给定查询qIItem的集合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进行尝试),但是以下是相关详细信息:

  1. 创建一个Dump,它用作表示Car类对象的lambda参数(与Query类的字段无关)

    ParameterExpression

  2. 创建var parameterExpression = Expression.Parameter(typeof(Car),"c");以访问在查询中使用的Car类的每个相关字段(这需要Query类的Field属性)

    MemberExpression
  3. 完成表达式:

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);
  1. 将所有内容组合在一起以形成复合表达式:

表达式a。)和b。)组合如下:

c.Year >= 2015

表达式c。)是独立的:

var yearConstantExpression = Expression.Constant(2015);

var yearGreaterThanEqualExpression = Expression.GreaterThanOrEqual(yearMemberAccessExpression, yearConstantExpression);
  1. 最终((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

的where子句中使用。

设计建议

  1. 使用上面的说明以及Query Class占位符或直接从Dictionary的值,可以创建任意数量的表达式以在代码中动态使用,进行编译并用作Func委托
  2. Linq这样的二进制表达式都由表达式树公开,可以直接使用。对于像Func<Car,bool>这样的默认情况下可用的方法,您需要Reflection来获得b Equal, GreaterThan, LessThan, LessThanOrEqual,GreaterThanOrEqual,类似地,它可以是完成了静态方法,只是有对象表达式
  3. 所有表达式都希望按正确的顺序从左到右提供值,不能是随机或错误的顺序,否则它将在运行时失败。
  4. 在您的情况下,由于您在括号中合并的查询很少且独立的查询很少,因此我建议创建多个Contains,其中每个列表都使用MethodInfoList<Expression>在括号中合并,每个列表因此可以使用AndAlso
  5. 进行组合

通过这种方法,您将能够在运行时使用Linq表达式构造非常复杂的需求。