Как анализировать фильтр OData $ с регулярным выражением в C #?

Привет, мне интересно, какой лучший подход должен был бы проанализировать строку фильтра OData $ в C #, например

/ API / организации? $ Filter = “имя eq ‘Facebook’ или имя eq ‘Twitter’ и подписчики gt ’30′”

Следует вернуть все организации с именем Facebook или Twitter и у которых более 30 подписчиков. Я исследовал довольно много, но не могу найти решения, которые не вращаются вокруг WCF. Я думал использовать Regex и группировать их, поэтому у меня есть список classов Filter, которые:

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

но я в тупике, как обрабатывать ANDs / ORs.

Проверьте это регулярное выражение с флагами i и x .

 (? (?.+?)\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, представляющего предложение фильтра.

(Чтобы получить входные данные model и type , вы можете проанализировать свой файл метаданных $, используя что-то вроде этого:

 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, вы можете пересечь дерево AST, которое возвращается FilterClause.

Например, вы можете получить FilterClause из параметров запроса controllerа:

 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», но для моих целей он работал хорошо. YMMV. 😉

Я думаю, вы должны перенести AST с интерфейсом, предоставляемым с использованием шаблона посетителя.

Учтите, что у вас есть этот class, который представляет собой фильтр

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

Итак, как мы «извлекаем» фильтры, которые поставляются с параметрами OData для вашего classа?

Ну, объект FilterClause имеет свойство Expression, которое является SingleValueNode, которое наследуется от QueryNode. У QueryNode есть метод Accept, который принимает QueryNodeVisitor.

  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 

должны содержать фильтры в желаемом формате. Я уверен, что требуется больше работы, но если вы сможете получить этот каток, я думаю, вы можете понять это.