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.
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.
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.
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
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.
I hat the same issue a while ago and solved it with the following approach:
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
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;
}
Converting $skip
and $top
is as easy as using Skip()
and Take()
.
ExpressionVisitor
that replaces the argument of the MethodCallExpression
.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).
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