Now and then you need to post-process OData queries, e.g. to „correct“ entity values or to filter query results that could not be filtered on database side. The problem: how to do it? Normally you have a controller action with return type IQueryable<T>
and you leave it for further filtering to OData… you just don’t have the query result at this moment.
The first thing we have to do is executing the OData query in C#… this is possible by internalizing the [EnableQuery(...)]
attribute in the controller method, which gets a new auto-resolved parameter of type ODataQueryOptions<TEntity>
. Then, you can call options.ApplyTo(...)
to manually execute the given OData query:
public IList<MyEntity> GetEntities(ODataQueryOptions<MyEntity> options) { var validationSettings = new ODataValidationSettings { AllowedQueryOptions = AllowedQueryOptions.Filter | AllowedQueryOptions.Expand, AllowedFunctions = AllowedFunctions.None, ... }; options.Validate(validationSettings); IQueryable<MyEntity> query = GetQuery(); var result = options.ApplyTo(query, new ODataQuerySettings()); return result; }
Now you could think: great, let’s post-process the result and then return it. Well, unfortunately it’s not this easy. If the OData query includes an $expand
, then the result will not be of type MyEntity
, but of type SelectAllAndExpand<MyEntity>
. Furthermore, this framework class is private, so you don’t have a chance to cast to it and evaluate it strongly typed. This is really shitty… there is a public API that returns a private implementation without providing a public interface or something for it. In my opinion, it’s a bad example of framework design…
So, to evaluate it and get an IList<MyEntity>
, we are forced to use reflection to get the resulting entity out of the private SelectAllAndExpand
class:
var resultList = new List<MyEntity>(); var result = options.ApplyTo(query, new ODataQuerySettings()); foreach (var item in result) { if (item is MyEntity) { resultList.Add((MyEntity)item); } else if (item.GetType().Name == "SelectAllAndExpand`1") { var entityProperty = item.GetType().GetProperty("Instance"); resultList.Add((MyEntity)entityProperty.GetValue(item)); } }
Finally, it’s easy to post-process the resultList as IList<MyEntity>
as you want.
6 Kommentare
-
Thank you! This works perfectly. Life-saver.
-
There is an issue where if you specify $select, you cannot get the returned items, because SelectExpandWrapper is returned instead. Does this solve that issue https://github.com/OData/WebApi/issues/521
-
FYI: I used the code in this answer (http://stackoverflow.com/a/30552434/592449) to convert the the response into a dictionary, map the dictionary to my model and return an IList to the client. Works beautifully.
-
Works this by $select …..
-
Perfect! Thank you!
Pingbacks
-
[…] I’ve solved it after I came across this post: http://www.jauernig-it.de/intercepting-and-post-processing-odata-queries-on-the-server/ […]