如何用C#中的正则表达式解析OData $ filter?

嗨,我想知道在C#中解析OData $filter字符串的最佳方法是什么

/ API / organizations?$ filter =“name eq’Facebook’或name eq’Twitter’和subscriber gt’30”

应该返回所有名称为Facebook或Twitter且拥有超过30个订阅者的组织。 我已经研究了很多,但找不到任何不围绕WCF的解决方案。 我正在考虑使用Regex并对它们进行分组,因此我有一个Filter类列表,这样:

Filter Resource: Name Operator: Eq Value: Facebook Filter Resource: Name Operator: Eq Value: Twitter Filter Resource: Subscribers Operator: gt Value: 30 

但我很难过如何处理ANDs / ORs。

使用标志ix检查此正则表达式。

 (? (?.+?)\s+ (?eq|ne|gt|ge|lt|le|add|sub|mul|div|mod)\s+ '?(?.+?)'? ) (?: \s*$ |\s+(?:or|and|not)\s+ ) 

演示

http://regexhero.net/tester/?id=0a26931f-aaa3-4fa0-9fc9-1a67d34c16b3

示例代码

 string strRegex = @"(?" + "\n" + @" (?.+?)\s+" + "\n" + @" (?eq|ne|gt|ge|lt|le|add|sub|mul|div|mod)\s+" + "\n" + @" '?(?.+?)'?" + "\n" + @")" + "\n" + @"(?:" + "\n" + @" \s*$" + "\n" + @" |\s+(?:or|and|not)\s+" + "\n" + @")" + "\n"; Regex myRegex = new Regex(strRegex, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); string strTargetString = @"name eq 'Facebook' or name eq 'Twitter' and subscribers gt '30'"; string strReplace = @"Filter >> ${Filter}" + "\n" + @" Resource : ${Resource}" + "\n" + @" Operator : ${Operator}" + "\n" + @" Value : ${Value}" + "\n\n"; return myRegex.Replace(strTargetString, strReplace); 

产量

 Filter >> name eq 'Facebook' Resource : name Operator : eq Value : Facebook Filter >> name eq 'Twitter' Resource : name Operator : eq Value : Twitter Filter >> subscribers gt '30' Resource : subscribers Operator : gt Value : 30 

讨论

为了使资源和运算符具有大写,请使用MatchEvaluator 。 但是,不支持使用()进行分组。 如果您希望正则表达式支持它,请留下评论。

在.NET中,有一个可以为您执行此操作的库。 编写自己的正则表达式可能会遗漏一些边缘情况。

使用NuGet,引入Microsoft.Data.OData。 然后,你可以这样做:

 using Microsoft.Data.OData.Query; var result = ODataUriParser.ParseFilter( "name eq 'Facebook' or name eq 'Twitter' and subscribers gt 30", model, type); 

这里的result将是表示过滤子句的AST的forms。

(要获取modeltype输入,您可以使用以下内容解析$ metadata文件:

 using Microsoft.Data.Edm; using Microsoft.Data.Edm.Csdl; IEdmModel model = EdmxReader.Parse(new XmlTextReader(/*stream of your $metadata file*/)); IEdmEntityType type = model.FindType("organisation"); 

在Jen S所说的基础上,您可以遍历FilterClause返回的AST树。

例如,您可以从控制器的查询选项中检索FilterClause:

 public IQueryable GetModelObjects(ODataQueryOptions queryOptions) { var filterClause = queryOptions.Filter.FilterClause; 

然后,您可以使用以下代码遍历生成的AST树(从本文中借用):

 var values = new Dictionary(); TryNodeValue(queryOptions.Filter.FilterClause.Expression, values); 

调用的函数是这样的:

 public void TryNodeValue(SingleValueNode node, IDictionary values) { if (node is BinaryOperatorNode ) { var bon = (BinaryOperatorNode)node; var left = bon.Left; var right = bon.Right; if (left is ConvertNode) { var convLeft = ((ConvertNode)left).Source; if (convLeft is SingleValuePropertyAccessNode && right is ConstantNode) ProcessConvertNode((SingleValuePropertyAccessNode)convLeft, right, bon.OperatorKind, values); else TryNodeValue(((ConvertNode)left).Source, values); } if (left is BinaryOperatorNode) { TryNodeValue(left, values); } if (right is BinaryOperatorNode) { TryNodeValue(right, values); } if (right is ConvertNode) { TryNodeValue(((ConvertNode)right).Source, values); } if (left is SingleValuePropertyAccessNode && right is ConstantNode) { ProcessConvertNode((SingleValuePropertyAccessNode)left, right, bon.OperatorKind, values); } } } public void ProcessConvertNode(SingleValuePropertyAccessNode left, SingleValueNode right, BinaryOperatorKind opKind, IDictionary values) { if (left is SingleValuePropertyAccessNode && right is ConstantNode) { var p = (SingleValuePropertyAccessNode)left; if (opKind == BinaryOperatorKind.Equal) { var value = ((ConstantNode)right).Value; values.Add(p.Property.Name, value); } } } 

然后,您可以浏览列表字典并检索您的值:

  if (values != null && values.Count() > 0) { // iterate through the filters and assign variables as required foreach (var kvp in values) { switch (kvp.Key.ToUpper()) { case "COL1": col1 = kvp.Value.ToString(); break; case "COL2": col2 = kvp.Value.ToString(); break; case "COL3": col3 = Convert.ToInt32(kvp.Value); break; default: break; } } } 

这个例子相当简单,因为它只考虑“eq”评估,但就我的目的而言,它运作良好。 因人而异。 ;)

我认为你应该使用访问者模式提供的接口来运行AST。

考虑一下这个类代表了一个filter

 public class FilterValue { public string ComparisonOperator { get; set; } public string Value { get; set; } public string FieldName { get; set; } public string LogicalOperator { get; set; } } 

那么,我们如何“提取”OData参数附带的filter到您的类?

那么FilterClause对象有一个Expression属性,它是一个inheritance自QueryNode的SingleValueNode。 QueryNode具有接受QueryNodeVisitor的Accept方法。

  public virtual T Accept(QueryNodeVisitor visitor); 

是的,所以你必须实现自己的QueryNodeVisitor并做你的事情。 下面是一个未完成的示例(我不会覆盖所有可能的访问者)。

 public class MyVisitor : QueryNodeVisitor where TSource: class { List filterValueList = new List(); FilterValue current = new FilterValue(); public override TSource Visit(BinaryOperatorNode nodeIn) { if(nodeIn.OperatorKind == Microsoft.Data.OData.Query.BinaryOperatorKind.And || nodeIn.OperatorKind == Microsoft.Data.OData.Query.BinaryOperatorKind.Or) { current.LogicalOperator = nodeIn.OperatorKind.ToString(); } else { current.ComparisonOperator = nodeIn.OperatorKind.ToString(); } nodeIn.Right.Accept(this); nodeIn.Left.Accept(this); return null; } public override TSource Visit(SingleValuePropertyAccessNode nodeIn) { current.FieldName = nodeIn.Property.Name; //We are finished, add current to collection. filterValueList.Add(current); //Reset current current = new FilterValue(); return null; } public override TSource Visit(ConstantNode nodeIn) { current.Value = nodeIn.LiteralText; return null; } } 

然后,开火:)

 MyVisitor visitor = new MyVisitor(); options.Filter.FilterClause.Expression.Accept(visitor); 

当它遍历了你的树

 visitor.filterValueList 

应包含所需格式的filter。 我确定需要做更多的工作,但如果你能够实现这一目标,我想你可以搞清楚。