Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Parse OData $filter in C#

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?

like image 384
user7932844 Avatar asked Jul 04 '18 12:07

user7932844


People also ask

How does $filter work in OData?

Querying Data Using $filter. You can use filter expressions in OData requests to filter and return only those results that match the expressions specified. You do this by adding the $filter system query option to the end of the OData request.

What is Odataqueryoptions?

OData defines parameters that can be used to modify an OData query. The client sends these parameters in the query string of the request URI. For example, to sort the results, a client uses the $orderby parameter: http://localhost/Products?$orderby=Name. The OData specification calls these parameters query options.

What is OData URL?

The Open Data Protocol (OData) enables the creation of REST-based data services, which allow resources, identified using Uniform Resource Identifiers (URLs) and defined in a data model, to be published and edited by Web clients using simple HTTP messages.


1 Answers

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: enter image description here

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.

like image 130
timur Avatar answered Oct 21 '22 13:10

timur