Search code examples
asp.net-web-apiodatabreeze

Breeze 1.4.6 + Web API 2 OData - Navigation properties missing


I'm trying to use the latest version of Breeze at the time of writing (1.4.6) with Web API 2 (ODataController). My problem is that the result from Breeze is missing navigation properties.

Consider the following:

public class Country : EntityBase<long>
{
    public Country()
    {
        Provinces = new List<Province>();
    }

    [Required]
    [StringLength(256)]
    public string Name { get; set; }

    [Required]
    [StringLength(3)]
    public string Abbreviation { get; set; }

    // Navigation Properties

    [InverseProperty("Country")]
    public virtual ICollection<Province> Provinces { get; set; }
}

public class Province : EntityBase<long>
{
    [Required]
    [StringLength(256)]
    public string Name { get; set; }

    [Required]
    [StringLength(3)]
    public string Abbreviation { get; set; }

    public long CountryId { get; set; }

    [ForeignKey("CountryId")]
    [InverseProperty("Provinces")]
    public virtual Country Country { get; set; }
}

I then scaffolded out an ODataController for Country using VS 2013 and made sure my WebApiConfig was updated correctly:

var builder = new ODataConventionModelBuilder();
builder.EntitySet<Country>("Countries");
builder.EntitySet<Province>("Provinces");
builder.Namespace = "MyNamespace.Models";

var batchHandler = new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer);
config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel(), batchHandler);

At this point I'm able to run queries successfully against my data.

http://mysite.local/Api/odata/Countries?$expand=Provinces

This returns what you would expect:

 {
  "odata.metadata":"http://mysite.local/Api/odata/$metadata#Countries","value":[
    {
      "Provinces":[
        {
          "Name":"Alberta","Abbreviation":"AB","CountryId":"1","Id":"1"
        },{
          "Name":"British Columbia","Abbreviation":"BC","CountryId":"1","Id":"2"
        },{
          "Name":"Manitoba","Abbreviation":"MB","CountryId":"1","Id":"3"
        },{
          "Name":"New Brunswick","Abbreviation":"NB","CountryId":"1","Id":"4"
        }
        // edited for brevity
      ],"Name":"Canada","Abbreviation":"CA","Id":"1"
  }
}

Now I'm ready to start consuming this data on the client. I hooked up datajs/Breeze and configured breeze to use OData. Here's what I'm using to configure and create the breeze manager:

    function configureBreezeManager() {
        breeze.config.initializeAdapterInstances({
            dataService: 'OData'
        });
        breeze.NamingConvention.camelCase.setAsDefault();
        var mgr = new breeze.EntityManager('http://mysite.local/api/odata');
        return mgr;
    }

Then I tried to do a simple query:

var query = breeze.EntityQuery.from('Countries').expand('Province').orderBy('name');

This query succeeds and I get a result with a couple country objects (Canada & US) but it is missing navigation properties for Provinces. The other data (id, name, abbreviation) are all present as expected. I also tried the inverse of this and queried Provinces and expanded Country and got the same result (no navigation properties).

I can see that Breeze is correctly appending $expand and the request url is what I would expect.

I've read that OData is missing foreign key information that Breeze requires but Breeze 1.4.4 added support for OData v3 so I'm not sure if this should be working or not?

Thanks.

Edit: Here is the generated metadata in case it helps:

<edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0">
  <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0">
    <Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm" Namespace="MyNamespace.Models">
      <EntityType Name="Country">
        <Key>
          <PropertyRef Name="Id"/>
        </Key>
        <Property Name="Name" Type="Edm.String" Nullable="false"/>
        <Property Name="Abbreviation" Type="Edm.String" Nullable="false"/>
        <Property Name="Id" Type="Edm.Int64" Nullable="false"/>
        <NavigationProperty Name="Provinces" Relationship="MyNamespace.Models.MyNamespace_Models_Country_Provinces_MyNamespace_Models_Province_ProvincesPartner" ToRole="Provinces" FromRole="ProvincesPartner"/>
      </EntityType>
      <EntityType Name="Province">
        <Key>
          <PropertyRef Name="Id"/>
        </Key>
        <Property Name="Name" Type="Edm.String" Nullable="false"/>
        <Property Name="Abbreviation" Type="Edm.String" Nullable="false"/>
        <Property Name="CountryId" Type="Edm.Int64" Nullable="false"/>
        <Property Name="Id" Type="Edm.Int64" Nullable="false"/>
        <NavigationProperty Name="Country" Relationship="MyNamespace.Models.MyNamespace_Models_Province_Country_MyNamespace_Models_Country_CountryPartner" ToRole="Country" FromRole="CountryPartner"/>
      </EntityType>
      <Association Name="MyNamespace_Models_Country_Provinces_MyNamespace_Models_Province_ProvincesPartner">
        <End Type="MyNamespace.Models.Province" Role="Provinces" Multiplicity="*"/>
        <End Type="MyNamespace.Models.Country" Role="ProvincesPartner" Multiplicity="0..1"/>
      </Association>
      <Association Name="MyNamespace_Models_Province_Country_MyNamespace_Models_Country_CountryPartner">
        <End Type="MyNamespace.Models.Country" Role="Country" Multiplicity="0..1"/>
        <End Type="MyNamespace.Models.Province" Role="CountryPartner" Multiplicity="0..1"/>
      </Association>
      <EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
        <EntitySet Name="Countries" EntityType="MyNamespace.Models.Country"/>
        <EntitySet Name="Provinces" EntityType="MyNamespace.Models.Province"/>
        <AssociationSet Name="MyNamespace_Models_Country_Provinces_MyNamespace_Models_Province_ProvincesPartnerSet" Association="MyNamespace.Models.MyNamespace_Models_Country_Provinces_MyNamespace_Models_Province_ProvincesPartner">
          <End Role="ProvincesPartner" EntitySet="Countries"/>
          <End Role="Provinces" EntitySet="Provinces"/>
        </AssociationSet>
        <AssociationSet Name="MyNamespace_Models_Province_Country_MyNamespace_Models_Country_CountryPartnerSet" Association="MyNamespace.Models.MyNamespace_Models_Province_Country_MyNamespace_Models_Country_CountryPartner">
          <End Role="CountryPartner" EntitySet="Provinces"/>
          <End Role="Country" EntitySet="Countries"/>
        </AssociationSet>
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

Solution

  • The problem here is that Microsoft's ODataConventionModelBuilder does not currently expose OData constraints for EDMX backed models. They are aware of this, and claim that they plan to do so in some later release.

    In the meantime, you have a couple of options.

    1) Ignore the ODataConventionBuilder and use the default Breeze WebApi2 implementation to talk directly to an instance of a BreezeController that wraps your EFModel. This actually supports a superset of what the ODataConventionModelBuilder would provide, without any additional complexity. See any of the Breeze examples in the Breeze zip. ( especially the DocCode example).

    2) Use a WCF DataService to expose your EF model as an OData service. WCF DataServices do expose OData contraints.

    We are planning on providing additional documentation on your options in each of these areas sometime in the "near" future. So please stay tuned.