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.
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");
)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With