Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

$select and $expand break ODataQueryOptions -- how to fix?

We are using Microsoft ASP.NET MVC OData WebAPI for our web services. Because of some data architecture issues surrounding hierarchy ID (which are outside the scope of this conversation), some of our GET operations have to use ODataQueryOptions and manually manipulate the expression to add additional restrictions. We do so like this (error handling code removed and calls to other methods inlined for clarity):

public IQueryable<Person> Get(ODataQueryOptions<Person> oDataQueryOptions)
{
    IQueryable<Person> result;
    IQueryable<Person> dataSet = context.Persons;

    var tempQuery = oDataQueryOptions.ApplyTo(dataSet).Cast<Person>();
    var modifier = new HierarchyNodeExpressionVisitor(GetDescendantsOfNode, GetAncestorsOfNode);
    var expression = modifier.ModifyHierarchyNodeExpression(tempQuery.Expression);

    result = context.Persons.Provider.CreateQuery<Person>(expression);

    return result;
}

This has worked great for some time, but we've been eagerly awaiting select-and-expand so that we can better control the data we receive from our services. Monday we updated our dev environment to WebApi OData 5.0.0-rc1 and got select-and-expand working, but we can't use it against these services that use ODataQueryOptions. We can only use it against our other services. If we query the code above using $select and/or $expand, we get the following error:

"message": "The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.",
"type": "System.InvalidOperationException",
"stacktrace": "",
"internalexception":
{
    "message": "Unable to cast the type 'System.Web.Http.OData.Query.Expressions.SelectAllAndExpand`1' to type 'OurCompany.Domains.Data.Models.Person'. LINQ to Entities only supports casting EDM primitive or enumeration types.",
    "type": "System.NotSupportedException",
    "stacktrace": " at System.Data.Objects.ELinq.ExpressionConverter.ValidateAndAdjustCastTypes(TypeUsage toType, TypeUsage fromType, Type toClrType, Type fromClrType) at System.Data.Objects.ELinq.ExpressionConverter.GetCastTargetType(TypeUsage fromType, Type toClrType, Type fromClrType, Boolean preserveCastForDateTime) at System.Data.Objects.ELinq.ExpressionConverter.CreateCastExpression(DbExpression source, Type toClrType, Type fromClrType) at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.CastMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call) at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq) at System.Data.Objects.ELinq.ExpressionConverter.Convert() at System.Data.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption) at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption) at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable.GetEnumerator() at System.Data.Entity.Infrastructure.DbQuery`1.System.Collections.IEnumerable.GetEnumerator() at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, IEdmTypeReference feedType, ODataWriter writer, ODataSerializerContext writeContext) at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders) at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.WebHost.HttpControllerHandler.d__10.MoveNext()"
}

I've done some Googling and stumbled upon this and this, but neither were helpful. Nobody appears to be doing quite what we're doing and trying to use select-and-expand. How do we fix this? I'm at a loss here...

like image 372
Nick Williams Avatar asked Sep 18 '13 18:09

Nick Williams


1 Answers

The problem is in this line of code,

var tempQuery = oDataQueryOptions.ApplyTo(dataSet).Cast<Person>();

The Cast is invalid as once $select and $expand are applied the result is no longer a Person. It would be a Wrapper<Person> that contains only the properties that the client asked for. You might have to modify your HierarchyNodeExpressionVisitor to take this into consideration.

Also, try changing your action to this to handle the fact that the result might not be a IQueryable<Person> anymore.

    public IHttpActionResult Get(ODataQueryOptions<Person> oDataQueryOptions)
    {
        IQueryable result;
        IQueryable<Person> dataSet = context.Persons;

        IQueryable tempQuery = oDataQueryOptions.ApplyTo(dataSet);
        var modifier = new HierarchyNodeExpressionVisitor(GetDescendantsOfNode, GetAncestorsOfNode);
        var expression = modifier.ModifyHierarchyNodeExpression(tempQuery.Expression);

        result = context.Persons.Provider.CreateQuery(expression);

        return Ok(result, result.GetType());
    }

    private IHttpActionResult Ok(object content, Type type)
    {
        Type resultType = typeof(OkNegotiatedContentResult<>).MakeGenericType(type);
        return Activator.CreateInstance(resultType, content, this) as IHttpActionResult;
    }
like image 199
RaghuRam Nadiminti Avatar answered Nov 11 '22 20:11

RaghuRam Nadiminti