为什么我的Breeze.js实体没有创建ko.observables?

时间:2013-07-26 22:38:46

标签: knockout.js breeze

我在没有服务器端组件的情况下使用Breeze.js,并使用以下代码在客户端创建实体。按照Ward的要求,我简化了一切,并提供了更多信息。我的MetaDataStore配置功能 -

function configureMetadataStore(metadataStore) {
        metadataStore.addEntityType({
            shortName: 'Manufacturer',
            namespace: 'StackAndReach',
            autoGeneratedKeyType: breeze.AutoGeneratedKeyType.Identity,
            dataProperties: {
                id: { dataType: DT.Int64, isPartOfKey: true },
                name: { dataType: DT.String },
                website: { dataType: DT.String},
                approved: {dataType: DT.boolean},
                user_id: { dataType: DT.Int64 }
            }
        });
    }

我的服务器的JSON响应

{"id":"141","name":"Trek","website":"http:\/\/www.trekbikes.com\/","approved":"1","user_id":"3"}

我的datacontext中的配置代码(整个设置减去服务器元数据的缺失是在John Papa的课程之后设置的)

var entityQuery = breeze.EntityQuery,
        manager = configureBreezeManager();
function configureBreezeManager() {
        breeze.NamingConvention.camelCase.setAsDefault();
        var ds = new breeze.DataService({
            serviceName: config.remoteServiceName,
            hasServerMetadata: false
        });

        var mgr = new breeze.EntityManager({dataService : ds});
        model.configureMetadataStore(mgr.metadataStore);
        return mgr;
    }

当模型被拉下来时,数据就在那里,但是数据没有包含在ko.observables中,并且init函数中的ko.observables / ko.computed不在传递出查询的模型中。如何确保模型的数据包含在ko.observables中并添加了ko.computeds?

1 个答案:

答案 0 :(得分:11)

这个答案同样是一个分析问题的教程,作为问题的答案。

第1步 - 简化和隔离

让我们彻底简化,直到找到缺失的步骤。让我们从你拥有的最简单的实体类型开始......最多有2到5个属性。没有一个?做一个。将Manufacturer缩减为" id"和"名称"。我们首先尝试让机制失效。

您没有在服务器上使用任何微风组件。精细。选择提供该测试实体数据的服务器端点。让该端点只提供一个实例的JSON数组。向我们展示到达客户端的JSON ...整个JSON有效负载,就像它到达线路一样。应该简短;如果不是,你还没有足够的简化。

那么我们可以弄清楚你是否需要一个JsonResultsAdapter以及它应该是什么样子。

向我们展示使用EntityType,ctor和初始化程序填充metadataStore的确切顺序。坦率地说,在我们让第一个工作之前,我宁愿没有ctor或初始化器。

如何确保您创建的EntityManager正在使用该商店?我们需要查看您的配置代码以及如何新建EntityManager并使用它来查询端点。

如果您遵循我的建议,那么代码就不会很多。也许二十行。 JSON提要大约应该是10行。如果你无法点击这些数字,那么你还不够简单。

第2步 - 查看更简单的示例

现在你重新开始了这个例子,我更清楚地知道在哪里看。

有两件事情在我身上跳了出来:

  1. 来自服务器的JSON结果
  2. camelCase命名惯例
  3. 来自服务器的JSON

    让我们打印您提供的JSON结果并进行讨论:

    {
      "id": "141",
      "name": "Trek",
      "website": "http:\/\/www.trekbikes.com\/",
      "approved": "1",
      "user_id": "3"
    }
    

    Breeze不知道如何处理该JSON对象,因为它缺少类型信息。微风它只是一个任意对象,也许是投影的结果。

    将其与查询DocCode Web API的查询的JSON结果进行比较。这是为查询生成的网址:

    >http://localhost:47595/breeze/northwind/Suppliers/?$top=1
    

    这里是(缩写)JSON结果

    [
       {
          "$id":"1",
          "$type":"Northwind.Models.Supplier, DocCode.Models",
          "SupplierID":1,
          "CompanyName":"Exotic Liquids"
       }
    ]
    

    默认情况下 Breeze客户端需要使用JSON.NET(ASP.NET中的默认序列化程序)序列化的数据。

    JSON.NET有效负载是节点或节点数组。 JSON.NET为每个节点添加了自己的$id$type属性。

    我希望您将注意力集中在 $type 属性上,您可以将其视为.NET类型的全名(class-with-namespace,assembly-name)。

    你可以在没有$id财产的情况下离开。

      

    $ id是一个自动递增的序列化密钥。通常,同一对象在有效载荷中出现多次。 JSON.NET不是重复内容,而是替换像{$ref: #}这样的简单节点,其中#指的是较早节点的$id。这种方法既减少了有效负载大小又打破了循环引用。

    但Breeze真的很期待那个$type财产。这就是它将JSON对象/节点连接到元数据中的类型的方式。如果您的制造商示例节点有一个,它可能是这样的:

    "$type": "StackAndReach.Manufacturer, MyModel"
    

    我不知道您是如何在服务器上序列化数据的。看起来你使用的不是JSON.NET。

    很酷。我只是告诉你Breeze默认是如何工作的;它非常友好。 但Breeze并不需要.NET 。它是一个纯JavaScript库。你只需告诉它你想要的东西。

    使用 toType(...)

    您可以做的最简单的事情是add toType to your query

    var query = breeze.EntityQuery.from('Manufacturers')
                      .where( ... )
                      .toType( 'Manufacturer' );
    

    通过这种方式,您明确说明了'制造商'返回的顶级节点。端点包含您在元数据中描述的Manufacturer类型的数据。

    我打赌这会立即为您服务(一旦您修复了下面描述的命名约定问题)。

    这是一种有效的方法,但它有几个缺点。我提到两个:

    1. 您必须记住将其添加到每个查询中。

    2. 它仅适用于顶级实体;如果不适用于嵌套实体,例如在应用.expand()子句时返回的实体。

    3. 我更喜欢教Breeze客户端如何使用自定义JsonResultsAdapter自行解释JSON结果。

      自定义JsonResultsAdapter

      查看Breeze客户端使用Edmunds车辆信息服务中的数据的Breeze Edmunds Sample

      Edmunds服务器发送完全不同类型的JSON有效负载以响应查询。这是一个片段:

      {
         "makeHolder":[
            {
               "id":200347864,
               "models":[
                  {
                     "link":"/api/vehicle/am-general/hummer",
                     "id":"AM_General_Hummer",
                     "name":"Hummer"
                  }
               ],
               "name":"AM General",
               "niceName":"amgeneral",
               "manufacturer":null,
               "attributeGroups":{
      
               }
            },
            ... more ...
         ]
      }
      

      也没有$type。 Breeze的开发人员做了什么?他在 app / jsonResultsAdapter.js 文件中写了一个custom Breeze JsonResultsAdapter

      我不会在这里重现那个文件,尽管它只有40行。我希望你阅读jsonResultsAdapter documentation,下拉Edmunds样本,并亲自阅读。

      我将总结它的作用以及它的工作原理。 Breeze在收到JSON有效负载时会先调用jsonResultsAdapter,然后再处理该有效负载中的每个节点。你的工作是告诉Breeze如何通过调整节点本身并返回描述节点的元对象来处理你所做的节点。

      这是一个片段:

      >if (node.id && node.models) {
          // move 'node.models' links so 'models' can be empty array
          node.modelLinks = node.models;
          node.models = [];
          return { entityType: "Make"  }
      }
      

      此代码段中有三项活动:

      1. 确定节点的内容(if ...
      2. 调整节点值(无论出于什么原因对你有意义)
      3. 撰写并返回" meta"对象结果。
      4. 专注于#3 。开发人员告诉Breeze"将此节点变为Make实体。

        结构类型匹配

        你可能会说," Hey Ward,Manufacturer实体类型与JSON对象结构完全匹配。 Breeze应该将其识别为Manufacturer。"

        Breeze不会通过匹配类型结构来实现实体类型。我认为它也不应该......因为不同的类型通常具有相同的结构。例如:我的StatusCodeProductCode实体类型都是{ id: int, name: string}。我们还有许多其他改进工作要做;应对类型歧义并不在我们的名单上。

        命名惯例

        最后,让我们回到我看到的另一个问题。

        您的configureBreezeManager方法开始:

        breeze.NamingConvention.camelCase.setAsDefault();

        您已经从"同一客户端和服务器"更改了默认命名约定。 to" pascalCase-on-client / CamelCase-on-server"。

        通过切换到camelCase约定,您告诉Breeze应将客户端属性foo作为Foo发送到服务器。

        这是正确的做法吗?如果您的服务器期望CamelCase属性名称。但是,根据JSON有效负载中的属性名称,服务器也需要CamelCase。客户端和服务器上的属性名称相同。如果breeze向制造商发送Name属性值而不是name属性值,则会发生错误。

        保持微风默认,"什么都不做"公约到位。不要覆盖它。从configureBreezeManager

        中删除该pascalCase约定行

        保存更改

        我们一直在讨论查询结果。我们还没有谈到您如何将更改保存回服务器。

        我确定你有自己的协议(像ReST一样?)和序列化格式。这是一个完全不同的讨论。让我们不要在Stack Overflow问题中进入。我只是提醒你,你很快就会对这个问题感到头疼。