Breeze / breeze.js

Breeze for JavaScript clients
MIT License
537 stars 88 forks source link

$metadata handling: entity types in separate schema from container not supported #96

Closed johncrim closed 8 years ago

johncrim commented 9 years ago

We commonly manage our domain model types separate from our containers, and this setup is not working with Breeze.js using the webApiOData data service adapter. Using Breeze.Client 1.5.4.

I've modified the "Todo-Angular-Breeze" example to match this setup, here's the ~/odata/$metadata:

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
  <edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <Schema Namespace="ODataBreezejsSample.Models" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityType Name="TodoItem" p5:ClrType="ODataBreezejsSample.Models.TodoItem, ODataBreezejsSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns:p5="http://schemas.microsoft.com/ado/2013/11/edm/customannotation">
        <Key>
          <PropertyRef Name="Id" />
        </Key>
        <Property Name="Id" Type="Edm.Int32" Nullable="false" p7:StoreGeneratedPattern="Identity" xmlns:p7="http://schemas.microsoft.com/ado/2009/02/edm/annotation" />
        <Property Name="TodoListId" Type="Edm.Int32" Nullable="false" />
        <Property Name="Description" Type="Edm.String" Nullable="false" MaxLength="60" FixedLength="false" Unicode="true" />
        <Property Name="IsDone" Type="Edm.Boolean" />
        <NavigationProperty Name="TodoList" Relationship="ODataBreezejsSample.Models.ODataBreezejsSample_Models_TodoItem_TodoList_ODataBreezejsSample_Models_TodoList_TodoItems" ToRole="TodoList" FromRole="TodoItems" />
      </EntityType>
      <EntityType Name="TodoList" p5:ClrType="ODataBreezejsSample.Models.TodoList, ODataBreezejsSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns:p5="http://schemas.microsoft.com/ado/2013/11/edm/customannotation">
        <Key>
          <PropertyRef Name="Id" />
        </Key>
        <Property Name="Id" Type="Edm.Int32" Nullable="false" p7:StoreGeneratedPattern="Identity" xmlns:p7="http://schemas.microsoft.com/ado/2009/02/edm/annotation" />
        <Property Name="Title" Type="Edm.String" Nullable="false" MaxLength="30" FixedLength="false" Unicode="true" />
        <Property Name="Created" Type="Edm.DateTime" Nullable="false" />
        <NavigationProperty Name="TodoItems" Relationship="ODataBreezejsSample.Models.ODataBreezejsSample_Models_TodoItem_TodoList_ODataBreezejsSample_Models_TodoList_TodoItems" ToRole="TodoItems" FromRole="TodoList" />
      </EntityType>
      <Association Name="ODataBreezejsSample_Models_TodoItem_TodoList_ODataBreezejsSample_Models_TodoList_TodoItems">
        <End Type="ODataBreezejsSample.Models.TodoList" Role="TodoList" Multiplicity="1">
          <OnDelete Action="Cascade" />
        </End>
        <End Type="ODataBreezejsSample.Models.TodoItem" Role="TodoItems" Multiplicity="*" />
        <ReferentialConstraint>
          <Principal Role="TodoList">
            <PropertyRef Name="Id" />
          </Principal>
          <Dependent Role="TodoItems">
            <PropertyRef Name="TodoListId" />
          </Dependent>
        </ReferentialConstraint>
      </Association>
    </Schema>
    <Schema Namespace="ODataBreezejsSample.Db" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityContainer Name="TodoListContext" p5:UseClrTypes="true" m:IsDefaultEntityContainer="true" xmlns:p5="http://schemas.microsoft.com/ado/2013/11/edm/customannotation">
        <EntitySet Name="TodoItems" EntityType="ODataBreezejsSample.Models.TodoItem" />
        <EntitySet Name="TodoLists" EntityType="ODataBreezejsSample.Models.TodoList" />
        <AssociationSet Name="ODataBreezejsSample_Models_TodoItem_TodoList_ODataBreezejsSample_Models_TodoList_TodoItemsSet" Association="ODataBreezejsSample.Models.ODataBreezejsSample_Models_TodoItem_TodoList_ODataBreezejsSample_Models_TodoList_TodoItems">
          <End Role="TodoItems" EntitySet="TodoItems" />
          <End Role="TodoList" EntitySet="TodoLists" />
        </AssociationSet>
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

Note that the EntityContainer and EntityTypes are in different Schemas. This is using web API odata, but that's not central to this issue. I am fixing up the metadata so that it's not broken in the ways that web API odata normally breaks Breeze. The EDM entitytypes have a different namespace from the container, which is why they end up in different schemas in the metadata document.

By default if I run the TodoList sample with this setup, the error is:

"Unable to locate a 'Type' by the name: 'TodoList:#ODataBreezejsSample.Db'. Be sure to execute a query or call fetchMetadata first."

It appears that the error is in breeze.debug.js line 7324:

  // schema is only needed for navProperty type name
  function parseTypeNameWithSchema(entityTypeName, schema) {
    var result = parseTypeName(entityTypeName);
    if (schema) {
      var ns = getNamespaceFor(result.shortTypeName, schema);
      if (ns) {
        result = makeTypeHash(result.shortTypeName, ns);
      }
    }
    return result;
  }

which is called from CsdlMetadataParser.parse(...). The parameter values are:

entityTypeName = "ODataBreezejsSample.Models.TodoItem"
schema = {...  namespace = "ODataBreezejsSample.Db"

This line works as it should:

  var result = parseTypeName(entityTypeName);

At this point, result is:

  ?result
{...}
    __proto__: {...}
    namespace: "ODataBreezejsSample.Models"
    shortTypeName: "TodoItem"
    typeName: "TodoItem:#ODataBreezejsSample.Models"

Then the call:

 var ns = getNamespaceFor(result.shortTypeName, schema);

Which then causes the returned result to be:

     __proto__: {...}
    namespace: "ODataBreezejsSample.Db"
    shortTypeName: "TodoItem"
    typeName: "TodoItem:#ODataBreezejsSample.Db"

which is the incorrect typeName.

If I change line 7327 to:

    if (schema && !result.namespace) {

Everything works. I believe this is the correct behavior, in that if the EntityType name is fully scoped, its namespace should be used; and if the EntityType is name only, the surrounding schema namespace should be used.

johncrim commented 9 years ago

Here's a related SO post, in case it's helpful feedback to show other examples of developers with entitytypes with different namespaces from the container:

Breeze assumes EntityType is in same namespace as EntityContainer. Shouldn’t Breeze consider if the EntityType value is a qualified type name?

wardbell commented 9 years ago

That sounds sensible. We'll take a closer look.

johncrim commented 9 years ago

An example is posted here:

https://entityrepository.codeplex.com/SourceControl/latest#examples/Todo-Angular-Breeze/

Note that this is based on your Todo-Angular-Breeze example, but dependencies have been updated to latest, and it uses entityrepository, which provides a number of additions/enhancements on top of web api odata. Proper breeze support is important to this project, as it's one of the clients we want to use/support.

Compared to the standard version of this example project, a bunch of unnecessary code was deleted, and entityrepository configuration was added. The benefits of entityrepository include:

steveschmitt commented 8 years ago

I couldn't use your change directly, because it broke some of our non-OData tests which require CSpace-to-OSpace mapping of the namespaces. Please try my fix and see if it works for you.

Thanks a lot for your work on this.