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

时间:2014-01-30 16:23:10

标签: c# regex odata

您好我想知道在C#中解析OData $过滤字符串的最佳方法是什么,例如

/ API / organizations?$ filter =“name eq'Facebook'或name eq'Twitter'和订阅者gt'30'”

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

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

但我对如何处理ANDs / OR感到难过。

5 个答案:

答案 0 :(得分:15)

在.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的形式。

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

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");

答案 1 :(得分:13)

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

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

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

然后,您可以使用以下代码(从this article借用)遍历生成的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;
                }
            }
        }

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

答案 2 :(得分:11)

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

考虑一下这个代表过滤器的类

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

那么,我们如何&#34;提取&#34; OData参数附带到您班级的过滤器?

FilterClause对象有一个Expression属性,它是一个继承自QueryNode的SingleValueNode。 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

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

答案 3 :(得分:5)

使用标记 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。 但是,不支持使用()进行分组。如果您希望正则表达式支持,请留下评论。

答案 4 :(得分:0)

感谢@Stinky Buffalo 的回答。 在字典中添加重复键时,我更改了您的代码并解决了错误。

示例:

CreateDate%20gt%202021-05-22T00:00:00Z%20and%20CreateDate%20lt%202021-05-26T00:00:00Z%20

BookRequestType%20eq%20%27BusDomestic%27%20or%20BookRequestType%20eq%20%27TrainDomestic%27%20or%20BookRequestType%20eq%20%27FlightDomestic%27%20

以下代码对我来说效果很好:

首先安装 Install-Package Microsoft.Data.OData -Version 5.8.4 软件包。

然后创建名为“ODataHelper”的类,然后复制以下代码:

   public static Dictionary<string, object> ODataUriParser(ODataQueryOptions<T> queryOptions)
    {
        var dictFilters = new Dictionary<string, object>();
        TryNodeValue(queryOptions.Filter?.FilterClause?.Expression, dictFilters);

        return dictFilters;
    }

    private static void TryNodeValue(SingleValueNode node, IDictionary<string, object> dictFilters)
    {
        if (node is null)
            return;

        if (node is SingleValueFunctionCallNode valueFunction)
        {
            ParseSingleFunctionNode(valueFunction, dictFilters);
        }

        if (node is BinaryOperatorNode binaryOperatorNode)
        {
            var left = binaryOperatorNode.Left;
            var right = binaryOperatorNode.Right;

            if (left is SingleValuePropertyAccessNode leftNodeRight && right is ConstantNode rightNodeRight)
            {
                ParseSingleValueNode(leftNodeRight, rightNodeRight, binaryOperatorNode.OperatorKind, dictFilters);
            }

            switch (left)
            {
                case ConvertNode node1:
                {
                    var convertLeft = node1.Source;

                    if (convertLeft is SingleValuePropertyAccessNode leftNodeLeft &&
                        right is ConstantNode rightNodeLeft)
                    {
                        ParseSingleValueNode(
                            leftNodeLeft,
                            rightNodeLeft,
                            binaryOperatorNode.OperatorKind,
                            dictFilters);
                    }
                    else
                        TryNodeValue(node1.Source, dictFilters);

                    break;
                }
                case BinaryOperatorNode:
                    TryNodeValue(left, dictFilters);
                    break;
                case SingleValueFunctionCallNode functionNode:
                    ParseSingleFunctionNode(functionNode, dictFilters);
                    break;
            }

            switch (right)
            {
                case BinaryOperatorNode:
                    TryNodeValue(right, dictFilters);
                    break;
                case ConvertNode convertNode:
                    TryNodeValue(convertNode.Source, dictFilters);
                    break;
                case SingleValueFunctionCallNode functionNode:
                    ParseSingleFunctionNode(functionNode, dictFilters);
                    break;
            }
        }
    }

    private static void ParseSingleValueNode(
        SingleValuePropertyAccessNode left,
        SingleValueNode right,
        BinaryOperatorKind operatorKind,
        IDictionary<string, object> dictFilters)
    {
        if (left is SingleValuePropertyAccessNode leftNode && right is ConstantNode rightNode)
        {
            var key = leftNode.Property.Name;
            var value = rightNode.Value;

            if (operatorKind is BinaryOperatorKind.GreaterThan
                or BinaryOperatorKind.GreaterThanOrEqual
                or BinaryOperatorKind.LessThan
                or BinaryOperatorKind.LessThanOrEqual)
            {
                if (!dictFilters.TryAdd(key, value))
                {
                    dictFilters.TryAdd($"{key}_To", value);
                }
            }

            else if (value is ODataEnumValue enumValue)
            {
                if (dictFilters.TryGetValue(key, out object currentValue))
                {
                    dictFilters[key] = $"{currentValue},{enumValue.Value}";
                }
                else
                {
                    dictFilters.Add(key, enumValue.Value);
                }
            }

            else
                dictFilters.Add(key, value);
        }
    }

    private static void ParseSingleFunctionNode(
        SingleValueFunctionCallNode node,
        IDictionary<string, object> dictFilters)
    {
        if (node is SingleValueFunctionCallNode singleNode)
        {
            var key = (singleNode.Parameters.First() as SingleValuePropertyAccessNode)?.Property.Name;
            var value = (singleNode.Parameters.Last() as ConstantNode)?.Value;


        if (string.IsNullOrEmpty(Convert.ToString(value)?.Trim()))
            return;

            dictFilters.TryAdd(key, value);
        }
    }

对于调用 ODataUriParser 方法,您需要从输入操作中获取值。

从请求 api 中获取 ODataQueryOptions> :

输入端点操作 => ODataQueryOptions<YourObjectModel> options

然后使用以下代码:

 Dictionary<string,object> result = ODataHelper<YourObjectModel>.ODataUriParser(options);