Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parse OData $filter with regular expression in C#?

Tags:

c#

regex

odata

Hi I'm wondering what the best approach would be to parse an OData $filter string in C#, for example

/API/organisations?$filter="name eq 'Facebook' or name eq 'Twitter' and subscribers gt '30'"

Should return all organisations with a name of Facebook or Twitter and who have more than 30 subscribers. I've researched quite a bit but can't find any solutions which don't revolve around WCF. I was thinking of using Regex and grouping them so I have a list of Filter classes such that:

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

but I'm stumped as how to handle ANDs / ORs.

like image 696
Nick Spicer Avatar asked Jan 30 '14 16:01

Nick Spicer


2 Answers

In .NET, there's a library available that will do this for you. Writing your own regex runs the risk of missing some edge case.

Using NuGet, bring in Microsoft.Data.OData. Then, you can do:

using Microsoft.Data.OData.Query;

var result = ODataUriParser.ParseFilter(
  "name eq 'Facebook' or name eq 'Twitter' and subscribers gt 30",
  model,
  type);

result here will be in the form of an AST representing the filter clause.

(To get the model and type inputs, you could parse your $metadata file using something like this:

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

)

like image 115
Jen S Avatar answered Sep 22 '22 02:09

Jen S


I think you are supposed to travserse the AST with the interface provided using the visitor pattern.

Consider you have this class that represents a filter

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

So, how do we "extract" the filters that comes with the OData parameters to your class?

Well the FilterClause object have an Expression property which is a SingleValueNode wich inherits from a QueryNode. The QueryNode have the Accept method who takes a QueryNodeVisitor.

    public virtual T Accept<T>(QueryNodeVisitor<T> visitor);

Right, so you must implement your own QueryNodeVisitor and do your stuff. Below is a non finished example (I dont override all possible visitors).

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;
    }

}

Then, fire away :)

MyVisitor<object> visitor = new MyVisitor<object>();
options.Filter.FilterClause.Expression.Accept(visitor);

When it has traversed the tree your

visitor.filterValueList

should contain the filters in your desired format. Im sure more work is needed but if you can get this rolling I think you can figure it out.

like image 29
PvPlatten Avatar answered Sep 23 '22 02:09

PvPlatten