I'm using OData framework 5.0.0 and Web API 5.0.0 and EntityFramework 5.0.0. And have problem with expand on navigation property which is a collection. I always get the following exception:
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.
The given key was not present in the dictionary.
at System.Web.Http.OData.Query.Expressions.SelectExpandWrapper`1.GetEdmType()
at System.Web.Http.OData.Formatter.Serialization.ODataSerializerContext.GetEdmType(Object instance, Type type)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteEntry(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, IEdmTypeReference feedType, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteExpandedNavigationProperty(KeyValuePair`2 navigationPropertyToExpand, EntityInstanceContext entityInstanceContext, ODataWriter writer)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteExpandedNavigationProperties(IDictionary`2 navigationPropertiesToExpand, EntityInstanceContext entityInstanceContext, ODataWriter writer)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteEntry(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
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.
Expand on non-collection navigation properties works like a charm.
These are my EF classes:
public partial class ElementTemplate
{
public ElementTemplate()
{
this.Elements = new HashSet<Element>();
this.Derived = new HashSet<ElementTemplate>();
this.TemplateAttributes = new HashSet<ElementTemplateAttribute>();
}
public System.Guid ID { get; set; }
public string InheritancePath { get; set; }
public string Name { get; set; }
public short Level { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public bool AllowElementToExtend { get; set; }
public Nullable<System.Guid> DefaultElementTemplateAttributeID { get; set; }
public Nullable<System.Guid> BaseElementTemplateID { get; set; }
public string SecurityDescriptor { get; set; }
public Nullable<System.DateTime> CheckOutTime { get; set; }
public string CheckOutUserName { get; set; }
public string CheckOutMachineName { get; set; }
public virtual ICollection<Element> Elements { get; set; }
public virtual ICollection<ElementTemplate> Derived { get; set; }
public virtual ElementTemplate Base { get; set; }
public virtual ElementTemplateAttribute DefaultTemplateAttribute { get; set; }
public virtual ICollection<ElementTemplateAttribute> TemplateAttributes { get; set; }
}
public partial class Element
{
public Element()
{
this.Attributes = new HashSet<ElementAttribute>();
}
public System.Guid ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Comment { get; set; }
public int Revision { get; set; }
public bool HasChildren { get; set; }
public bool HasMultipleVersions { get; set; }
public Nullable<System.Guid> ElementTemplateID { get; set; }
public Nullable<System.Guid> DBReferenceTypeID { get; set; }
public string SecurityDescriptor { get; set; }
public System.DateTime Created { get; set; }
public string CreatedBy { get; set; }
public System.DateTime Modified { get; set; }
public string ModifiedBy { get; set; }
public Nullable<System.DateTime> CheckOutTime { get; set; }
public string CheckOutUserName { get; set; }
public string CheckOutMachineName { get; set; }
public virtual ICollection<ElementAttribute> Attributes { get; set; }
public virtual ElementTemplate Template { get; set; }
}
The metadata for ElementTemplate looks as follows:
<EntityType Name="ElementTemplate">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false" />
<Property Name="InheritancePath" Type="Edm.String" />
<Property Name="Name" Type="Edm.String" />
<Property Name="Level" Type="Edm.Int16" Nullable="false" />
<Property Name="Description" Type="Edm.String" />
<Property Name="Type" Type="Edm.String" />
<Property Name="AllowElementToExtend" Type="Edm.Boolean" Nullable="false" />
<Property Name="DefaultElementTemplateAttributeID" Type="Edm.Guid" />
<Property Name="BaseElementTemplateID" Type="Edm.Guid" />
<Property Name="SecurityDescriptor" Type="Edm.String" />
<Property Name="CheckOutTime" Type="Edm.DateTime" />
<Property Name="CheckOutUserName" Type="Edm.String" />
<Property Name="CheckOutMachineName" Type="Edm.String" />
<NavigationProperty Name="Elements" Relationship="Entity_EntityModel_ElementTemplate_Elements_EntityModel_Element_ElementsPartner" ToRole="Elements" FromRole="ElementsPartner" />
<NavigationProperty Name="Derived" Relationship="Entity_EntityModel_ElementTemplate_Derived_Entity_EntityModel_ElementTemplate_DerivedPartner" ToRole="Derived" FromRole="DerivedPartner" />
<NavigationProperty Name="Base" Relationship="Entity_EntityModel_ElementTemplate_Base_Entity_EntityModel_ElementTemplate_BasePartner" ToRole="Base" FromRole="BasePartner" />
<NavigationProperty Name="DefaultTemplateAttribute" Relationship="Entity_EntityModel_ElementTemplate_DefaultTemplateAttribute_Entity_EntityModel_ElementTemplateAttribute_DefaultTemplateAttributePartner" ToRole="DefaultTemplateAttribute" FromRole="DefaultTemplateAttributePartner" />
<NavigationProperty Name="TemplateAttributes" Relationship="Entity_EntityModel_ElementTemplate_TemplateAttributesEntity_EntityModel_ElementTemplateAttribute_TemplateAttributesPartner" ToRole="TemplateAttributes" FromRole="TemplateAttributesPartner" />
</EntityType>
The relevant methods in the controller looks as follows:
// GET odata/ElementTemplates
[Queryable]
public IQueryable<ElementTemplate> GetElementTemplates()
{
return Db.ElementTemplates;
}
// GET odata/ElementTemplates(guid'...')
[Queryable]
public SingleResult<ElementTemplate> GetElementTemplate([FromODataUri] Guid key)
{
return SingleResult.Create(Db.ElementTemplates.Where(elementtemplate => elementtemplate.ID == key));
}
// GET odata/ElementTemplates(guid'...')/Elements
[Queryable]
public IQueryable<Element> GetElements([FromODataUri] Guid key)
{
return Db.ElementTemplates.Where(m => m.ID == key).SelectMany(m => m.Elements);
}
The following queries work:
/odata/ElementTemplates(guid'...')/Elements
/odata/ElementTemplates?$expand=Base
/odata/ElementTemplates?$expand=DefaultTemplateAttribute
But when accessing a collection, I get the error mentioned above:
/odata/ElementTemplates?$expand=Elements
/odata/ElementTemplates(guid'...')?$expand=Elements
If I change the controller in a way it pre-loads the data:
// GET odata/ElementTemplates
[Queryable]
public IQueryable<ElementTemplate> GetElementTemplates()
{
return Db.ElementTemplates.ToList().AsQueryable();
}
// GET odata/ElementTemplates(guid'...')
[Queryable]
public SingleResult<ElementTemplate> GetElementTemplate([FromODataUri] Guid key)
{
return SingleResult.Create(Db.ElementTemplates.Where(elementtemplate => elementtemplate.ID == key).ToList().AsQueryable());
}
Then all queries work, but it obviously kills the performance.
The issue seems to be similar to Expanding collections with EntitySetController in MVC Web Api, but they state the problem is in NHibernate framework, which I'm obviously not using, so there must be something else, any idea?
I'm also not experiencing the problem when using the same EF model via WCF DataServices.
Thanks!
It turned out Web API adds a CASE expression containing the model ID to the query. My code did not evaluate this CASE expression correctly.