How to Parse OData $filter in C#

浪子不回头ぞ 提交于 2020-08-27 02:17:20

问题


Manipulate odata filter

How can i manipulate filter in the backend and want the key value pairs of the filter query parameters?

Expression would like below

"?$filter =((Name eq 'John' or Name eq 'Grace Paul') and (Department eq 'Finance and Accounting'))"

As there are 2 filters concatenated & how can i get the values like

Filter 1:
    Key: Name
    Operator: eq
    Value: Name

Operator: or

Filter 2:
    Key: Name
    Operator: eq
    Value: Grace Paul

Operator: and

Filter 3:
    Key: Department
    Operator: eq
    Value: Finance and Accounting

I tried with

  • ODataUriParser, but it doesn't seems to support in ASP.NET core 2.1 web api.
  • Regular Expression - using this stack overflow question, it doesn't seem to work in my case as my 3rd filter contains and in the value & so the regular expression fails.
  • ODataQueryOptions in the method, but it gives the raw text where it cannot be extracted to the key value pairs like mentioned.

I'm using ASP.NET Core 2.1 Web API with OData v4 integration

Is there a way to accomplish the above?


回答1:


You may want to consider defining your own parser and then walking the AST to get desired values. There are plenty tools to do that (see flex or bison for example). But in .net world Irony might be a viable option: it's available in .net standard 2.0 which I had no issues plugging into a .net core 2.1 console test project.

To start off, you normally need to define a grammar. Luckily, Microsoft have been kind enough to supply us with EBNF reference so all we have to do is to adapt it to Irony. I ended up implementing a subset of the grammar above that seems to cater for your example statement (and a bit above and beyond, feel free to cut it down).

using Irony.Parsing;

namespace irony_playground
{
    [Language("OData", "1.0", "OData Filter")]
    public class OData: Grammar
    {
        public OData()
        {
            // first we define some terms
            var identifier = new RegexBasedTerminal("identifier", "[a-zA-Z_][a-zA-Z_0-9]*");
            var string_literal = new StringLiteral("string_literal", "'");
            var integer_literal = new NumberLiteral("integer_literal", NumberOptions.IntOnly);
            var float_literal = new NumberLiteral("float_literal", NumberOptions.AllowSign|NumberOptions.AllowSign) 
                                        | new RegexBasedTerminal("float_literal", "(NaN)|-?(INF)");
            var boolean_literal = new RegexBasedTerminal("boolean_literal", "(true)|(false)");

            var filter_expression = new NonTerminal("filter_expression");
            var boolean_expression = new NonTerminal("boolean_expression");
            var collection_filter_expression = new NonTerminal("collection_filter_expression");
            var logical_expression = new NonTerminal("logical_expression");
            var comparison_expression = new NonTerminal("comparison_expression");
            var variable = new NonTerminal("variable");
            var field_path = new NonTerminal("field_path");
            var lambda_expression = new NonTerminal("lambda_expression");
            var comparison_operator = new NonTerminal("comparison_operator");
            var constant = new NonTerminal("constant");

            Root = filter_expression; // this is where our entry point will be. 

            // and from here on we expand on all terms and their relationships
            filter_expression.Rule = boolean_expression;

            boolean_expression.Rule = collection_filter_expression
                                      | logical_expression
                                      | comparison_expression
                                      | boolean_literal
                                      | "(" + boolean_expression + ")"
                                      | variable;
            variable.Rule = identifier | field_path;

            field_path.Rule = MakeStarRule(field_path, ToTerm("/"), identifier);

            collection_filter_expression.Rule =
                field_path + "/all(" + lambda_expression + ")"
                | field_path + "/any(" + lambda_expression + ")"
                | field_path + "/any()";

            lambda_expression.Rule = identifier + ":" + boolean_expression;

            logical_expression.Rule =
                boolean_expression + (ToTerm("and", "and") | ToTerm("or", "or")) + boolean_expression
                | ToTerm("not", "not") + boolean_expression;

            comparison_expression.Rule =
                variable + comparison_operator + constant |
                constant + comparison_operator + variable;

            constant.Rule =
                string_literal
                | integer_literal
                | float_literal
                | boolean_literal
                | ToTerm("null");

            comparison_operator.Rule = ToTerm("gt") | "lt" | "ge" | "le" | "eq" | "ne";

            RegisterBracePair("(", ")");
        }
    }
}

A bit of a hint: Irony comes with Grammar Explorer tool that allows you to load grammar dlls and debug with them, so I'd suggest you put your class in its own project. Then you would have easier time wrapping your head around the concepts:

after you're happy with the grammar, you need to reference it from your project and parse the input string:

class Program
{
    static void Main(string[] args)
    {
        var g = new OData();
        var l = new LanguageData(g);
        var r = new Parser(l);
        var p = r.Parse("((Name eq 'John' or Name eq 'Grace Paul') and (Department eq 'Finance and Accounting'))"); // here's your tree
        // this is where you walk it and extract whatever data you desire 
    }
}

Then, all you have to do is walk the resulting tree and apply your custom logic based on sytax node type. One example how to do that can be found in this SO answer.

Depending on your requirements, you might find this is going to be a total overkill for your purpose, or might actually find that level of control it gives you is exactly right.




回答2:


I know this is not the solution but sharing with you just in case if it helps you later. This is to match all values on the right side of ':'

/(?<=: )[\w ]+/gm


来源:https://stackoverflow.com/questions/51173380/how-to-parse-odata-filter-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!