I'm trying to put together a simple toy project using Entity Framework, WebAPI, OData, and an Angular client. Everything is working fine, except the navigation property that I have put on one of my models doesn't seem to be working. When I call my API using $expand, the returned entities do not have their navigation properties.
My classes are Dog and Owner, and look like this:
public class Dog
{
// Properties
[Key]
public Guid Id { get; set; }
public String Name { get; set; }
[Required]
public DogBreed Breed { get; set; }
public int Age { get; set; }
public int Weight { get; set; }
// Foreign Keys
[ForeignKey("Owner")]
public Guid OwnerId { get; set; }
// Navigation
public virtual Owner Owner { get; set; }
}
public class Owner
{
// Properties
public Guid Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public DateTime SignupDate { get; set; }
// Navigation
public virtual ICollection<Dog> Dogs { get; set; }
}
I also have my Dog controller set up to handle querying:
public class DogsController : ODataController
{
DogHotelAPIContext db = new DogHotelAPIContext();
#region Public methods
[Queryable(AllowedQueryOptions = System.Web.Http.OData.Query.AllowedQueryOptions.All)]
public IQueryable<Dog> Get()
{
var result = db.Dogs.AsQueryable();
return result;
}
[Queryable(AllowedQueryOptions = System.Web.Http.OData.Query.AllowedQueryOptions.All)]
public SingleResult<Dog> Get([FromODataUri] Guid key)
{
IQueryable<Dog> result = db.Dogs.Where(d => d.Id == key).AsQueryable().Include("Owner");
return SingleResult.Create(result);
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
I've seeded the database with a bit of sample data. All dog records have an OwnerId that matches the Id of an Owner in the Owners table.
Querying for the list of dogs using this works fine:
http://localhost:49382/odata/Dogs
I get a list of Dog entities, without the Owner navigation property.
Querying for the dogs with their owners using OData $expand does NOT work:
http://localhost:49382/odata/Dogs?$expand=Owner
My response is a 200 with all of the Dog entities, but none of them have an Owner property on them in the JSON.
If I query my metadata, I find that OData does seem to know about it:
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>
<Schema Namespace="DogHotelAPI.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityType Name="Dog">
<Key>
<PropertyRef Name="id" />
</Key>
<Property Name="id" Type="Edm.Guid" Nullable="false" />
<Property Name="name" Type="Edm.String" />
<Property Name="breed" Type="DogHotelAPI.Models.Enums.DogBreed" Nullable="false" />
<Property Name="age" Type="Edm.Int32" Nullable="false" />
<Property Name="weight" Type="Edm.Int32" Nullable="false" />
<Property Name="ownerId" Type="Edm.Guid" />
<NavigationProperty Name="owner" Type="DogHotelAPI.Models.Owner">
<ReferentialConstraint Property="ownerId" ReferencedProperty="id" />
</NavigationProperty>
</EntityType>
<EntityType Name="Owner">
<Key>
<PropertyRef Name="id" />
</Key>
<Property Name="id" Type="Edm.Guid" Nullable="false" />
<Property Name="name" Type="Edm.String" />
<Property Name="address" Type="Edm.String" />
<Property Name="phone" Type="Edm.String" />
<Property Name="signupDate" Type="Edm.DateTimeOffset" Nullable="false" />
<NavigationProperty Name="dogs" Type="Collection(DogHotelAPI.Models.Dog)" />
</EntityType>
</Schema>
<Schema Namespace="DogHotelAPI.Models.Enums" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EnumType Name="DogBreed">
<Member Name="AfghanHound" Value="0" />
<Member Name="AmericanStaffordshireTerrier" Value="1" />
<Member Name="Boxer" Value="2" />
<Member Name="Chihuahua" Value="3" />
<Member Name="Dachsund" Value="4" />
<Member Name="GermanShepherd" Value="5" />
<Member Name="GoldenRetriever" Value="6" />
<Member Name="Greyhound" Value="7" />
<Member Name="ItalianGreyhound" Value="8" />
<Member Name="Labrador" Value="9" />
<Member Name="Pomeranian" Value="10" />
<Member Name="Poodle" Value="11" />
<Member Name="ToyPoodle" Value="12" />
<Member Name="ShihTzu" Value="13" />
<Member Name="YorkshireTerrier" Value="14" />
</EnumType>
</Schema>
<Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="Container">
<EntitySet Name="Dogs" EntityType="DogHotelAPI.Models.Dog">
<NavigationPropertyBinding Path="owner" Target="Owners" />
</EntitySet>
<EntitySet Name="Owners" EntityType="DogHotelAPI.Models.Owner">
<NavigationPropertyBinding Path="dogs" Target="Dogs" />
</EntitySet>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
What could I be missing that is preventing my navigation preoprty from coming back with the rest of my model?
EDIT
To further isolate the problem I tried including the Owners in C# on the server side. I added this line in the Get method of my Dog controller:
var test = db.Dogs.Include("Owner").ToList();
With this I can debug and see that the related owners ARE being included. Each dog has the owner that is associated with it in this list.
Using .Include("Owner") on what is actually returned does not fix the problem - the properties still never reach the client.
This seems to mean that the navigation properties are working, but are not being sent back to the client. This seems like it wound indicate an issue with OData or WebAPI, I would guess, but I'm not sure what.
Also, I have added the following lines to Application_Start in my Global.asax file in order to handle circular navigation properties:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.All;
I did that in case a circular reference was somehow the culprit, but this changes nothing.
UPDATE
I noticed that making a call to
http://localhost:49382/odata/Dogs(abfd26a5-14d8-4b14-adbe-0a0c0ef392a7)/owner
works. This retrieves the owner associated with that dog. This further illustrates that my navigation properties are set up correctly, they just aren't being included in responses to calls using $expand.
UPDATE 2
Here is the register method of my WebApiConfig file:
public static void Register(HttpConfiguration config)
{
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional }
//);
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();
builder.EntitySet<Dog>("Dogs");
builder.EntitySet<Owner>("Owners");
config.EnableQuerySupport();
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: builder.GetEdmModel());
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing();
}