web-dev-qa-db-ja.com

C#で正規表現を使用してOData $ filterを解析する方法は?

こんにちは私は、たとえば、C#でOData $ filter文字列を解析するための最良のアプローチは何であるか疑問に思っています

/ API/organization?$ filter = "name eq'Facebook 'またはnameeq'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

しかし、私はAND/ORの処理方法に困惑しています。

14
Nick Spicer

.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入力を取得するには、次のようなものを使用して$ 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");

17
Jen S

Jen Sの発言に基づいて、FilterClauseによって返されるASTツリーをトラバースできます。

たとえば、コントローラーのクエリオプションから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;
                }
            }
        }

この例は、「eq」評価のみを説明するという点でかなり単純ですが、私の目的ではうまく機能しました。 YMMV。 ;)

13
Stinky Buffalo

私はあなたがビジターパターンを使用して提供されたインターフェースで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オブジェクトには、QueryNodeから継承するSingleValueNodeであるExpressionプロパティがあります。 QueryNodeには、QueryNodeVisitorを受け取るAcceptメソッドがあります。

    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

希望する形式のフィルターが含まれている必要があります。もっと作業が必要だと思いますが、これを実行できれば、理解できると思います。

12
PvPlatten