Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert an OData query string to .NET expression tree

Completely rewriting this question since I understand more now than I did before.

I am attempting to abstract out the conversion of an OData query string directly to .NET expression tree. There appear to be a number of questions and articles on this, but no answers that provide an abstract solution that relies soley on the Microsoft.Data.OData namespace (ie, all examples rely on WebAPI, Entity Framework, or some other library).

The answer that does the best at providing an abstract solution is here:

https://stackoverflow.com/a/21536369/701346

These two lines got me started:

IEdmModel model = EdmxReader.Parse(new XmlTextReader(/*stream of your $metadata file*/));
IEdmEntityType type = model.FindType("organisation");

After much toil, I've learned that OData requires an EDM in order to generate its own proprietary expression tree model. It's only a model. You have to traverse that model to ultimately generate your own expression tree.

So I've done all of this (sort of). I happened upon this article which showed me how to create a basic EDM without any navigation:

https://blogs.msdn.microsoft.com/alexj/2012/12/06/parsing-filter-and-orderby-using-the-odatauriparser/

Using that, I ended up creating an EDM generator that recursively reflects through a class to build the EDM. The problem is that this is ridiculously complex and there isn't much information online about how to create an EDM dynamically, so it doesn't define any navigation properties and only works with a single entity.

I then created an ODataExpressionVisitor that's modeled after System.Linq.Expressions.ExpressionVisitor. It works pretty good. It's able to take this OData query string:

var filter = ODataUriParser.ParseFilter(
    "(Name eq 'Oxford Mall' or Street eq '123 whatever ln') and Id eq 2",
    edmBuilder.Model, edmBuilder.Model.FindType(typeof(CustomerLocation).FullName));

And generate this expression:

(
    $CustomerLocation.Name == "Oxford Mall" || 
    $CustomerLocation.Street == "123 whatever ln"
) && 
$CustomerLocation.Id == 2

It works, too, because I can compile it to a delagate and pass a CustomerLocation object into it and it will return the proper true/false. I haven't tested it with EF6 or my other expression-based framework yet, though.

However, I think I am recreating a wheel here. There must be an existing means to 1) generate an convention-based EDM from a class alone and 2) convert the resulting OData expression tree to a .NET expression tree.

like image 734
oscilatingcretin Avatar asked Oct 17 '16 15:10

oscilatingcretin


People also ask

Is it possible to generate an expression tree from OData?

The answer that does the best at providing an abstract solution is here: These two lines got me started: After much toil, I've learned that OData requires an EDM in order to generate its own proprietary expression tree model. It's only a model. You have to traverse that model to ultimately generate your own expression tree.

What is the read mode of an OData query?

In the read mode, an OData query is sent to the library input, it is parsed using the Microsoft.OData.Core (ODataLib) into the ODataLib representation, which is translated into the standard expression tree. The query is parameterized (i.e. constant expressions are replaced with variables) and then passed to the data access adapter.

How do I create an odataqueryoptions from a string?

Create ODataQueryOptions from an ODATA query string, which can be achieved by constructing it from a URI (I described this in more detail with code examples in Modifying ODataQueryOptions on the fly

What is the use of OData library?

The library can be used for reading and editing data. In the read mode, an OData query is sent to the library input, it is parsed using the Microsoft.OData.Core (ODataLib) into the ODataLib representation, which is translated into the standard expression tree.


1 Answers

I hat the same issue a while ago and solved it with the following approach:

  1. Create ODataQueryOptions from an ODATA query string, which can be achieved by constructing it from a URI (I described this in more detail with code examples in Modifying ODataQueryOptions on the fly
  2. To convert the various parts of ODataQueryOptions like FilterQueryOption to an Expression (actually a MethodCallExpression) we can use ApplyTo in conjunction with an empty IQueryable (converting OrderByQueryOption is quite similar to this):

    public static Expression ToExpression<T>(this FilterQueryOption filterQueryOption)
    where T: class
    {
        IQueryable queryable = Enumerable.Empty<T>().AsQueryable();
        queryable = filterQueryOption.ApplyTo(queryable, new OdataQuerySettings());
    
        return queryable.Expression;
    }
    
  3. Converting $skip and $top is as easy as using Skip() and Take().

  4. Depending on the scenario, in order to make this expression usable as a Lambda we might have to use an ExpressionVisitor that replaces the argument of the MethodCallExpression.
  5. Creating a Lambda is then only a matter of taking the Body and the Parameter and create a new Expression.Lambda:

    var expressionLambda = Expression.Lambda<Func<T, bool>>
        (
            visitedMethodCallExpression.Body, 
            Expression.Parameter(typeof(T), "$it")
        );
    

For a more complete example see Converting ODataQueryOptions into LINQ Expressions in C# (but this uses EntityFramework in the end, so you might want to skip the rest).

like image 142
Ronald Rink 'd-fens' Avatar answered Oct 27 '22 05:10

Ronald Rink 'd-fens'