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

Nic*_*cer 13 c# regex odata

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

/ 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
Run Code Online (Sandbox Code Playgroud)

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

Jen*_*n S 16

在.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);
Run Code Online (Sandbox Code Playgroud)

result 这里将采用代表过滤子句的AST的形式.

(要获取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");
Run Code Online (Sandbox Code Playgroud)

)

  • 请注意:此解决方案使用 Odata V 3.0。(`Microsoft.Data.OData`)。较新的 OData V 4.0 需要“Microsoft.OData.Core” (2认同)

小智 13

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

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

public IQueryable<ModelObject> GetModelObjects(ODataQueryOptions<ModelObject> queryOptions)        
    {
        var filterClause = queryOptions.Filter.FilterClause;
Run Code Online (Sandbox Code Playgroud)

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

var values = new Dictionary<string, object>();
TryNodeValue(queryOptions.Filter.FilterClause.Expression, values);
Run Code Online (Sandbox Code Playgroud)

调用的函数是这样的:

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);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

 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;
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

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


PvP*_*ten 12

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

考虑您有这个代表过滤器的类

public class FilterValue
{
    public string ComparisonOperator { get; set; }
    public string Value { get; set; }
    public string FieldName { get; set; }
    public string LogicalOperator { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

那么,我们如何"提取"OData参数附带的过滤器到您的类?

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

    public virtual T Accept<T>(QueryNodeVisitor<T> visitor);
Run Code Online (Sandbox Code Playgroud)

是的,所以你必须实现自己的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;
    }

}
Run Code Online (Sandbox Code Playgroud)

然后,开火:)

MyVisitor<object> visitor = new MyVisitor<object>();
options.Filter.FilterClause.Expression.Accept(visitor);
Run Code Online (Sandbox Code Playgroud)

当它遍历了你的树

visitor.filterValueList
Run Code Online (Sandbox Code Playgroud)

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

  • 只需注意一句:如果您正在使用Odata V4.0,就像我正在做的那样,您需要使用`Microsoft.OData.Core.UriParser.TreeNodeKinds.BinaryOperatorKind.And`来代替 (2认同)