Как разобрать OData $filter с регулярным выражением В C#?
Привет мне интересно, какой лучший подход был бы для разбора строки фильтра OData $в C#, например
/ API / организации?$filter= "name eq' Facebook 'или name eq 'Twitter' и подписчики gt '30' "
Должны вернуть все организации с именем Facebook или Twitter и имеющие более 30 подписчиков. Я исследовал довольно много, но не могу найти никаких решений, которые не вращаются вокруг WCF. Я думал использовать регулярное выражение и группировать их, чтобы у меня был список
фильтра классы такие, что:
Filter
Resource: Name
Operator: Eq
Value: Facebook
Filter
Resource: Name
Operator: Eq
Value: Twitter
Filter
Resource: Subscribers
Operator: gt
Value: 30
Но я в тупике, как обращаться с ANDs / ORs.
4 ответов:
Проверьте это регулярное выражение с помощью флагов i и x.
(?<Filter> (?<Resource>.+?)\s+ (?<Operator>eq|ne|gt|ge|lt|le|add|sub|mul|div|mod)\s+ '?(?<Value>.+?)'? ) (?: \s*$ |\s+(?:or|and|not)\s+ )Демо
Http://regexhero.net/tester/?id=0a26931f-aaa3-4fa0-9fc9-1a67d34c16b3
Пример кода
string strRegex = @"(?<Filter>" + "\n" + @" (?<Resource>.+?)\s+" + "\n" + @" (?<Operator>eq|ne|gt|ge|lt|le|add|sub|mul|div|mod)\s+" + "\n" + @" '?(?<Value>.+?)'?" + "\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.Данные.Службы 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, Вы можете проанализировать файл $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");)
Основываясь на том, что говорит Джен С, вы можете пересечь дерево AST, возвращаемое FilterClause.
Например, вы можете получить FilterClause из параметров запроса контроллера:
public IQueryable<ModelObject> GetModelObjects(ODataQueryOptions<ModelObject> queryOptions) { var filterClause = queryOptions.Filter.FilterClause;Затем вы можете пересечь результирующее дерево AST с кодом, подобным следующему (заимствованному из этой статьи):
var values = new Dictionary<string, object>(); TryNodeValue(queryOptions.Filter.FilterClause.Expression, values);Вызываемая функция выглядит так:
public void TryNodeValue(SingleValueNode node, IDictionary<string, object> 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<string, object> 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; } } }Это пример довольно прост в том, что он учитывает только оценки "эквалайзера", но для моих целей он работал хорошо. YMMV. ;)
Я думаю, что вы должны травсерсировать AST с интерфейсом, предоставленным с использованием шаблона посетителя.
Считайте, что у вас есть этот класс, который представляет собой фильтр
public class FilterValue { public string ComparisonOperator { get; set; } public string Value { get; set; } public string FieldName { get; set; } public string LogicalOperator { get; set; } }Итак, как мы "извлекаем" фильтры, которые поставляются с параметрами OData в ваш класс?
Ну у объекта FilterClause есть свойство Expression, которое является SingleValueNode, который наследуется от QueryNode. У QueryNode есть метод Accept, который принимает QueryNodeVisitor.
public virtual T Accept<T>(QueryNodeVisitor<T> visitor);Правильно, поэтому вы должны реализовать свой собственный QueryNodeVisitor и делать свое дело. Ниже приведен незавершенный пример (я не переопределяю всех возможных посетителей).
public class MyVisitor<TSource> : QueryNodeVisitor<TSource> where TSource: class { List<FilterValue> filterValueList = new List<FilterValue>(); 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<object> visitor = new MyVisitor<object>(); options.Filter.FilterClause.Expression.Accept(visitor);Когда он пересечет дерево ваш
visitor.filterValueListДолжны содержать фильтры в желаемом формате. Я уверен, что требуется больше работы, но если вы можете получить этот прокат, я думаю, что вы можете понять это.
Comments