15943

Expanding collections using Web Api OData controller

Question:

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:

<blockquote>

/odata/ElementTemplates(guid'...')/Elements

/odata/ElementTemplates?$expand=Base

/odata/ElementTemplates?$expand=DefaultTemplateAttribute

</blockquote>

But when accessing a collection, I get the error mentioned above:

<blockquote>

/odata/ElementTemplates?$expand=Elements

/odata/ElementTemplates(guid'...')?$expand=Elements

</blockquote>

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 <a href="https://stackoverflow.com/questions/20440449/expanding-collections-with-entitysetcontroller-in-mvc-web-api" rel="nofollow">Expanding collections with EntitySetController in MVC Web Api</a>, 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!

Answer1:

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.

Recommend

  • Breeze + NHibernate Many-To-One Relation, avoid having to specify relation keys
  • CreateDocumentCollectionIfNotExistsAsync does not always work
  • Formatting Dice Roll Output Java
  • How to upload file to S3 from GAE (a horror story)
  • IllegalArgumentException on Collections.sort() method
  • Casting JSONArray to Iterable - Kotlin
  • RecyclerView programmatically click
  • SimpleDateFormat problems with 2 year date
  • zend smtp mail crashes after 100+ mails
  • How to handle the JAVA WatchService Overflow event?
  • C# SerialPort.Open() does not throw an exception if port is already open in Mono
  • Can't add target for UIButton - unrecognised selector sent to instance, despite method been in
  • dotnet core Method 'ValidateOptions' … does not have an implementation
  • Unable to deploy on Windows 10 Mobile
  • iOS 8.3 - Metal, found nil while unwrapping an Optional Value
  • Installing PySide - OSX
  • How to read files recursively in Java 7?
  • const char **a = {“string1”,“string2”} and pointer arithametic
  • Exclusive access established by another Thread Java smartcardio
  • Exception handling as per java coding standards
  • Visual Studio not stopping on an exception being thrown
  • unrecognized selector isPitched called
  • maven jboss-as:start A required class was missing … org/sonaty…/ArtifactResolutionException
  • Is there a way to call library thread-local init/cleanup on thread creation/destruction?
  • How do I add a File Type Association in a Windows Phone 8.1 app manifest?
  • Issue with routerLink directive
  • Where these are stored?
  • Intel-64 and ia32 atomic operations acquire-release semantics and GCC 5+
  • Zoom in and out of jPanel
  • Graphics.CopyFromScreen [Web application] + The handle is invalid
  • Firefox Extension - Monitor refresh and change of tab
  • Saving Changes After In-App Purchase Has Been Purchased
  • How to define custom class, title, and target in Link Browser for content elements and the new rte_c
  • Display images in Django
  • Problem deserializing objects from cache on MyBatis 3/Java
  • Ionic 2 storage is not cleaning up on uninstall - Only for signed APK
  • preg_replace Double Spaces to tab (\\t) at the beginning of a line
  • Updated Ionic CLI but shows previous version (Windows)
  • To display the title for the current loaction in map in iphone
  • Conditional In-Line CSS for IE and Others?