Quantcast
Channel: ASPNETWebStack Issue Tracker Rss Feed
Viewing all articles
Browse latest Browse all 7215

Commented Unassigned: $expand breaks use of Get(ODataQueryOptions) [1073]

$
0
0
Bug, or "how do I do this", in Web API OData:

Using the nightly builds to test out $expand support - note that I really really want to switch to Web API OData over WCF Data Services, because the extension points are soo much better, but I can't justify doing so until I can provide equivalent functionality.

I want to inject `ODataValidationSettings` (so I can manage them centrally instead of in per-controller attributes). I can't do that using EntitySetController, so I had to create my own base class. Here's one implementation:

``` C#

public abstract class DbSetController<TEntity, TKey, TDbContext> : ODataController
where TEntity : class
where TDbContext : DbContext
{
private readonly ODataValidationSettings _queryValidationSettings;
private readonly TDbContext _dbContext;

protected DbSetController(ODataValidationSettings queryValidationSettings, TDbContext dbContext)
{
_queryValidationSettings = queryValidationSettings;
_dbContext = dbContext;
}

protected virtual ODataValidationSettings QueryValidationSettings
{
get { return _queryValidationSettings; }
}

public virtual IQueryable<TEntity> Get(ODataQueryOptions<TEntity> queryOptions)
{
queryOptions.Validate(QueryValidationSettings);

IQueryable queryApplied = queryOptions.ApplyTo(_dbContext.Set<TEntity>());
return queryApplied.Cast<TEntity>();
}


```

This works fine for simple queries, like http://localhost/odata/Status?$top=2 .

However, it breaks as soon as I add an $expand or $select clause - I get:
``` xml
<m:internalexception>
<m:message>Unable to cast the type 'System.Web.Http.OData.Query.Expressions.SelectExpandWrapper`1' to type 'Scrum.Model.WorkItem'. LINQ to Entities only supports casting EDM primitive or enumeration types.</m:message>
<m:type>System.NotSupportedException</m:type>
<m:stacktrace> at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.ValidateAndAdjustCastTypes(TypeUsage toType, TypeUsage fromType, Type toClrType, Type fromClrType)&#xD;
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.GetCastTargetType(TypeUsage fromType, Type toClrType, Type fromClrType, Boolean preserveCastForDateTime)&#xD;
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.CreateCastExpression(DbExpression source, Type toClrType, Type fromClrType)&#xD;
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.CastMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)&#xD;
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SequenceMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, SequenceMethod sequenceMethod)&#xD;
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)&#xD;
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)&#xD;
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)&#xD;
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.Convert()&#xD;
at System.Data.Entity.Core.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)&#xD;
at System.Data.Entity.Core.Objects.ObjectQuery`1.&lt;&gt;c__DisplayClassb.&lt;GetResults&gt;b__a()&#xD;
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, Boolean throwOnExistingTransaction, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)&#xD;
at System.Data.Entity.Core.Objects.ObjectQuery`1.&lt;&gt;c__DisplayClassb.&lt;GetResults&gt;b__9()&#xD;
at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.ProtectedExecute[TResult](Func`1 func)&#xD;
at System.Data.Entity.Infrastructure.ExecutionStrategy.Execute[TResult](Func`1 func)&#xD;
at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)&#xD;
at System.Data.Entity.Core.Objects.ObjectQuery`1.&lt;System.Collections.Generic.IEnumerable&lt;T&gt;.GetEnumerator&gt;b__0()&#xD;
at System.Lazy`1.CreateValue()&#xD;
at System.Lazy`1.LazyInitValue()&#xD;
at System.Lazy`1.get_Value()&#xD;
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()&#xD;
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, ODataWriter writer, ODataSerializerContext writeContext)&#xD;
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObjectInline(Object graph, ODataWriter writer, ODataSerializerContext writeContext)&#xD;
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)&#xD;
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)&#xD;
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)&#xD;
--- End of stack trace from previous location where exception was thrown ---&#xD;
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)&#xD;
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)&#xD;
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()&#xD;
at System.Web.Http.WebHost.HttpControllerHandler.&lt;WriteBufferedResponseContentAsync&gt;d__10.MoveNext()</m:stacktrace>
</m:internalexception>
```

Figuring that it may be an issue with entity framework not liking the other types in its expression tree, I tried this implementation:

``` C#
public virtual HttpResponseMessage Get(ODataQueryOptions<TEntity> queryOptions)
{
queryOptions.Validate(QueryValidationSettings);

IQueryable queryApplied = queryOptions.ApplyTo(_dbContext.Set<TEntity>());
var list = new List<object>(100);
foreach (var o in queryApplied)
{
list.Add(o);
}
return Request.CreateResponse(list.Cast<TEntity>());
}
```

This breaks as follows:
``` xml

<?xml version="1.0" encoding="utf-8"?>
<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<m:code />
<m:message xml:lang="en-US">An error has occurred.</m:message>
<m:innererror>
<m:message>The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.</m:message>
<m:type>System.InvalidOperationException</m:type>
<m:stacktrace></m:stacktrace>
<m:internalexception>
<m:message>Unable to cast object of type 'System.Web.Http.OData.Query.Expressions.SelectExpandWrapper`1[Scrum.Model.WorkItem]' to type 'Scrum.Model.WorkItem'.</m:message>
<m:type>System.InvalidCastException</m:type>
<m:stacktrace> at System.Linq.Enumerable.&lt;CastIterator&gt;d__b1`1.MoveNext()&#xD;
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, ODataWriter writer, ODataSerializerContext writeContext)&#xD;
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObjectInline(Object graph, ODataWriter writer, ODataSerializerContext writeContext)&#xD;
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)&#xD;
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)&#xD;
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)&#xD;
--- End of stack trace from previous location where exception was thrown ---&#xD;
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)&#xD;
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)&#xD;
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()&#xD;
at System.Web.Http.WebHost.HttpControllerHandler.&lt;WriteBufferedResponseContentAsync&gt;d__10.MoveNext()</m:stacktrace>
</m:internalexception>
</m:innererror>
</m:error>
```

It looks like I can extract the element values from `SelectExpandWrapper`, but it's not clear that's the right way to go - please point me at a better example if there's a better approach.

Comments: That is the expected behavior. Once $select or $expand is applied to IQueryable<TEntity>, the result is no longer IQueryable<TEntity>. It becomes IQueryable<SelectExpandWrapper<TEntity>> as $select and $expand are shape changing queries. I have some sample code for supporting $select and $expand using ODataQueryOptions<T> [here](https://aspnetwebstack.codeplex.com/wikipage?title=%24select%20and%20%24expand%20support&referringTitle=Specs). Please refer to sample number 4. It is slightly more complicated than usual due to the missing Request.CreateResponse that takes runtime type.

Viewing all articles
Browse latest Browse all 7215

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>