Edit
I'm left now with only one error:
{
"error":{
"code":"","message":"An error has occurred.","innererror":{
"message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata.metadata=minimal'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{
"message":"Object reference not set to an instance of an object.","type":"System.NullReferenceException","stacktrace":" at NHibernate.Linq.Visitors.ExpressionKeyVisitor.VisitConstantExpression(ConstantExpression expression)\r\n at NHibernate.Linq.Visitors.ExpressionKeyVisitor.VisitBinaryExpression(BinaryExpression expression)\r\n at NHibernate.Linq.Visitors.ExpressionKeyVisitor.VisitConditionalExpression(ConditionalExpression expression)\r\n at NHibernate.Linq.Visitors.ExpressionKeyVisitor.VisitMemberExpression(MemberExpression expression)\r\n at NHibernate.Linq.Visitors.SelectJoinDetector.VisitMemberExpression(MemberExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitConditionalExpression(ConditionalExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at Remotion.Linq.Clauses.SelectClause.TransformExpressions(Func`2 transformation)\r\n at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)\r\n at NHibernate.Linq.Visitors.QueryModelVisitor.GenerateHqlQuery(QueryModel queryModel, VisitorParameters parameters, Boolean root)\r\n at NHibernate.Linq.NhLinqExpression.Translate(ISessionFactoryImplementor sessionFactory, Boolean filter)\r\n at NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)\r\n at NHibernate.Engine.Query.QueryExpressionPlan..ctor(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters, ISessionFactoryImplementor factory)\r\n at NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters)\r\n at NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow)\r\n at NHibernate.Impl.AbstractSessionImpl.CreateQuery(IQueryExpression queryExpression)\r\n at NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression)\r\n at NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression)\r\n at Remotion.Linq.QueryableBase`1.System.Collections.IEnumerable.GetEnumerator()\r\n at System.Web.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, IEdmTypeReference feedType, ODataWriter writer, ODataSerializerContext writeContext)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()"
}
}
}
}
I put together a simple example that illustrates the issue and posted it to the NH users Google group.
Edit re: @MartinSmit
Here's the NH code I mentioned in my SO comment below. I've added some C# comments to this code.
public class ExpressionKeyVisitor : ExpressionTreeVisitor
{
private readonly IDictionary<ConstantExpression, NamedParameter> _constantToParameterMap;
private ExpressionKeyVisitor(IDictionary<ConstantExpression, NamedParameter> constantToParameterMap)
{
// Don L: When this constructor is called, constantToParameterMap is null.
_constantToParameterMap = constantToParameterMap;
}
protected override Expression VisitConstantExpression(ConstantExpression expression)
{
NamedParameter param;
// Don L: The NullReferenceException occurs here because _constantToParameterMap is null.
if (_constantToParameterMap.TryGetValue(expression, out param) && insideSelectClause == false)
{
// Nulls generate different query plans. X = variable generates a different query depending on if variable is null or not.
if (param.Value == null)
_string.Append("NULL");
if (param.Value is IEnumerable && !((IEnumerable)param.Value).Cast<object>().Any())
_string.Append("EmptyList");
else
_string.Append(param.Name);
}
else
{
if (expression.Value == null)
_string.Append("NULL");
else
_string.Append(expression.Value);
}
return base.VisitConstantExpression(expression);
}
}
Original Question
I'm using the OData Web API to create OData 4.0 endpoints, and I'm having trouble with the $expand
option. I'm getting an exception when I try to expand:
http://localhost/MyApp/OData/Profile(2385)/AuthorshipsAsAuthor?$expand=Author/ProfileSubject
or
http://localhost/MyApp/OData/Profile(2385)/AuthorshipsAsAuthor?$expand=Author,Author/ProfileSubject
I get:
The query specified in the URI is not valid. Found a path traversing multiple navigation properties. Please rephrase the query such that each expand path contains only type segments and navigation properties.
I can use one-level expansion ($expand=Author,Publication
) with no problem. I've also tried $expand=Author($expand=ProfileSubject)
. That gets me:
The 'ObjectContent``1' type failed to serialize the response body for content type 'application/json; odata.metadata=minimal'.
I've seen OData v4 Web API 2.2 deep level expand not working, but that seems to be a problem with collections, whereas my data model is going from the many side to the one. I've simplified the data model a great deal, so there may be a transcription error.
public class Authorship : IMyDomainObject
{
public virtual Publication Publication { get; set; }
public virtual Profile Author { get; set; }
}
public class Profile : IMyDomainObject
{
public virtual InstitutionalAccount ProfileSubject { get; set; }
public virtual IList<Authorship> AuthorshipsAsAuthor { get; set; }
}
public abstract class Publication : IMyDomainObject
{
public virtual IList<Authorship> Authorships { get; set; }
}
I'm using reflection to set up my ODataConvetionModelBuilder
.
private static void SetUpODataModel(HttpConfiguration config)
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
Type oDataModelBuilderType = builder.GetType();
MethodInfo entitySetMethodInfo = oDataModelBuilderType.GetMethod("EntitySet");
Type domainObjectInterfaceType = typeof(IMyDomainObject);
// Ignore properties when appropriate.
var institutionalAccountEntityType =
builder.EntitySet<InstitutionalAccount>("InstitutionalAccount").EntityType;
institutionalAccountEntityType.Ignore(ia => ia.SomethingPrivate);
// This doesn't help.
//var authorshipEntityType = builder.EntitySet<Authorship>("Authorship").EntityType;
//authorshipEntityType.ContainsRequired(x => x.Author);
//var profileEntityType = builder.EntitySet<Profile>("Profile").EntityType;
//profileEntityType.ContainsRequired(x => x.ProfileSubject);
foreach (
Type domainObjectType
in
domainObjectInterfaceType
.Assembly
.DefinedTypes
.Where(t => t.GetInterfaces().Any(i => i.Equals(domainObjectInterfaceType)))
.Where(t => t.IsClass)
)
{
// Skip the type if we've already added it, e.g., it's already
// been done by hand.
if (builder.EntitySets.Any(es => es.ClrType.Equals(domainObjectType)))
{
continue;
}
// This is equivalent to:
// builder.EntitySet<MyDomainObjectType>("MyDomainObjectType");
MethodInfo entitySetGenericMethodInfo = entitySetMethodInfo.MakeGenericMethod(domainObjectType);
Object entitySet = entitySetGenericMethodInfo.Invoke(builder, new[] { domainObjectType.Name });
}
config.EnableEnumPrefixFree(true);
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "OData",
model: builder.GetEdmModel());
}
I'm using NHibernate, but I don't think that's relevant here.
edit: I found Failed to serialize the response body for content type without even including NHibernate in the search terms, but that didn't work.
edit: In response to @FanOuyang's comment, I'm still getting the error. My code now looks like
var w = new Authorship[] { /* big long mess with no circular references */ };
return w.AsQueryable();
edit: The test code I used didn't do what I thought it was doing. I wrote some new code that copies the result set to new objects, including circular references, and that seems to be working, so the problem does have something to do with NH. The errors I'm getting, depending on what I $expand, are:
{
"error":{
"code":"","message":"An error has occurred.","innererror":{
"message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata.metadata=minimal'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{
"message":"Argument types do not match","type":"System.ArgumentException","stacktrace":" at System.Linq.Expressions.Expression.Condition(Expression test, Expression ifTrue, Expression ifFalse)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitConditionalExpression(ConditionalExpression expression)\r\n at NHibernate.Linq.NestedSelects.SelectClauseRewriter.VisitExpression(Expression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at NHibernate.Linq.NestedSelects.SelectClauseRewriter.VisitExpression(Expression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at NHibernate.Linq.NestedSelects.SelectClauseRewriter.VisitExpression(Expression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at NHibernate.Linq.NestedSelects.SelectClauseRewriter.VisitExpression(Expression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at NHibernate.Linq.NestedSelects.SelectClauseRewriter.VisitExpression(Expression expression)\r\n at NHibernate.Linq.NestedSelects.NestedSelectRewriter.ReWrite(QueryModel queryModel, ISessionFactory sessionFactory)\r\n at NHibernate.Linq.Visitors.QueryModelVisitor.GenerateHqlQuery(QueryModel queryModel, VisitorParameters parameters, Boolean root)\r\n at NHibernate.Linq.NhLinqExpression.Translate(ISessionFactoryImplementor sessionFactory, Boolean filter)\r\n at NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)\r\n at NHibernate.Engine.Query.QueryExpressionPlan..ctor(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters, ISessionFactoryImplementor factory)\r\n at NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters)\r\n at NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow)\r\n at NHibernate.Impl.AbstractSessionImpl.CreateQuery(IQueryExpression queryExpression)\r\n at NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression)\r\n at NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression)\r\n at Remotion.Linq.QueryableBase`1.System.Collections.IEnumerable.GetEnumerator()\r\n at System.Web.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, IEdmTypeReference feedType, ODataWriter writer, ODataSerializerContext writeContext)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()"
}
}
}
}
or
{
"error":{
"code":"","message":"An error has occurred.","innererror":{
"message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata.metadata=minimal'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{
"message":"Object reference not set to an instance of an object.","type":"System.NullReferenceException","stacktrace":" at NHibernate.Linq.Visitors.ExpressionKeyVisitor.VisitConstantExpression(ConstantExpression expression)\r\n at NHibernate.Linq.Visitors.ExpressionKeyVisitor.VisitBinaryExpression(BinaryExpression expression)\r\n at NHibernate.Linq.Visitors.ExpressionKeyVisitor.VisitConditionalExpression(ConditionalExpression expression)\r\n at NHibernate.Linq.Visitors.ExpressionKeyVisitor.VisitMemberExpression(MemberExpression expression)\r\n at NHibernate.Linq.Visitors.SelectJoinDetector.VisitMemberExpression(MemberExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitConditionalExpression(ConditionalExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberAssignment(MemberAssignment memberAssigment)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitList[T](ReadOnlyCollection`1 list, Func`2 visitMethod)\r\n at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitMemberInitExpression(MemberInitExpression expression)\r\n at Remotion.Linq.Clauses.SelectClause.TransformExpressions(Func`2 transformation)\r\n at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)\r\n at NHibernate.Linq.Visitors.QueryModelVisitor.GenerateHqlQuery(QueryModel queryModel, VisitorParameters parameters, Boolean root)\r\n at NHibernate.Linq.NhLinqExpression.Translate(ISessionFactoryImplementor sessionFactory, Boolean filter)\r\n at NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)\r\n at NHibernate.Engine.Query.QueryExpressionPlan..ctor(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters, ISessionFactoryImplementor factory)\r\n at NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters)\r\n at NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow)\r\n at NHibernate.Impl.AbstractSessionImpl.CreateQuery(IQueryExpression queryExpression)\r\n at NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression)\r\n at NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression)\r\n at Remotion.Linq.QueryableBase`1.System.Collections.IEnumerable.GetEnumerator()\r\n at System.Web.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, IEdmTypeReference feedType, ODataWriter writer, ODataSerializerContext writeContext)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()"
}
}
}
}