How to convert an OData query string to .NET expression tree
Asked Answered
L

1

13

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://mcmap.net/q/686777/-how-to-parse-odata-filter-with-regular-expression-in-c

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.

Leclaire answered 17/10, 2016 at 15:10 Comment(9)
Check the following link. It's the list of official OData libraries in various languages. Maybe there's one library that supports your needs. odata.org/librariesTurbosupercharger
@Turbosupercharger That's a great link. I am reading odata.github.io/odata.net now and it's a great help. I will post an update once I finish this.Leclaire
I'm looking for something similar - did you manage to get this working? There are a couple of similar projects ("Linq To Querystring" and "Linq2Rest" but they only apply an OData query to an enumerable, not create a valid Expression Tree.Teressaterete
@Teressaterete Yes, but I had to write my own EDM builder, OData expression tree visitor, and then create my own expressions. Complete PIA. It's nothing I can share because of IP and all (wrote it on company time), but it was not easy. Charles above's link is what got me in the right directionLeclaire
@Teressaterete Someone recently brought Breeze to my attention which, at least at its root, converts a string query into a .NET expression tree. I don't know if they've modularized that particular function, though. If so, you might be able to leverage it. Otherwise, you'd need to write you own or utilize an entire framework just to get access to one piece of functionality. I'll also note that, if you go the route I did, you at least don't have to create an OData query parser since it does at least that for youLeclaire
Ta, I'll check out Breeze. I've built Expressions before for datatables.net querystrings by Dynamic Linq, so it might not be too tricky given there's already parser implementations for OData.Teressaterete
I wrote a dynamic OData EDM thing once also, total PIA. I think Roslyn code gen to make Types at runtime, then use OData's built in stuff would have been easierTeressaterete
Got something working using LinqToQueryString! share.linqpad.net/qwdoak.linqTeressaterete
Anyone have a solution to this 2+ years later? It blows my mind how many libraries are needed in Microsoft's stack to go from an odata filter/orderby string to an expression for a given TypeMizzle
S
7

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).

Seizing answered 11/3, 2017 at 20:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.