为什么我的 Breeze.js 实体没有创建 ko.observables? [英] Why are my Breeze.js entities not creating ko.observables?

查看:18
本文介绍了为什么我的 Breeze.js 实体没有创建 ko.observables?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的是没有服务器端组件的 Breeze.js,并使用以下代码在客户端创建了实体.根据 Ward 的要求,我已经简化了所有内容,并包含了更多信息.我的MetaDataStore配置功能-

function configureMetadataStore(metadataStore) {metadataStore.addEntityType({shortName: '制造商',命名空间:'StackAndReach',autoGeneratedKeyType: 微风.AutoGeneratedKeyType.Identity,数据属性:{id: { dataType: DT.Int64, isPartOfKey: true },名称:{ 数据类型:DT.String },网站:{数据类型:DT.String},批准:{数据类型:DT.boolean},用户 ID:{ 数据类型:DT.Int64 }}});}

来自我的服务器的 JSON 响应

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

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

var entityQuery =breed.EntityQuery,manager = configureBreezeManager();函数 configureBreezeManager() {微风.NamingConvention.camelCase.setAsDefault();var ds = 新风.DataService({服务名称:config.remoteServiceName,hasServerMetadata: 假});var mgr = 新风.EntityManager({dataService : ds});模型.configureMetadataStore(mgr.metadataStore);返回经理;}

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

解决方案

这个答案既是分析问题的教程,也是问题的答案.

第 1 步 - 简化和隔离

让我们彻底简化,直到找到遗漏的步骤.让我们从您拥有的最简单的实体类型开始……一个最多具有 2 到 5 个属性.没有吗?补一张.将 Manufacturer 减少到只有id"和name".我们正在努力首先解决机制问题.

您没有在服务器上使用任何微风组件.美好的.选择一个提供该测试实体数据的服务器端点.让该端点只提供一个实例的 JSON 数组.向我们展示到达客户端的 JSON……整个 JSON 有效负载,与它到达线路时完全一样.它应该简短;如果不是,则说明您还不够简化.

然后我们可以确定您是否需要 JsonResultsAdapter 以及如果需要它应该是什么样子.

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

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

如果你遵循我的建议,就不会有太多代码.二十行吧.JSON 提要大约有 10 行.如果你不能达到这些数字,说明你还不够简化.

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

既然您已经重新编写了示例,我就更好地了解该往哪里看.

有两件事让我眼前一亮:

  1. 来自服务器的 JSON 结果
  2. camelCase 命名约定

来自服务器的 JSON

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

<前>{"id": "141","name": "跋涉","网站": "http://www.trekbikes.com/","批准": "1",用户 ID":3"}

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

将其与命中 DocCode Web API 的查询的 JSON 结果进行比较.这是为查询生成的 URL:

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

这是(缩写的)JSON 结果

<前>[{"$id":"1","$type":"Northwind.Models.Supplier, DocCode.Models","供应商ID":1,公司名称":异国情调的液体"}]

默认 Breeze 客户端需要使用 JSON.NET,ASP.NET 中的默认序列化程序.

JSON.NET 有效负载是一个节点或一组节点.JSON.NET 将自己的 $id$type 属性添加到每个节点.

我想将您的注意力集中在 $type 属性上,您可以将其识别为 .网络类型.

如果没有 $id 属性,您也可以逃脱.

<块引用>

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

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

<前>"$type": "StackAndReach.Manufacturer, MyModel"

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

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

使用 toType(...)

您可以做的最简单的事情是添加toType 到您的查询.

<前>var 查询 = 微风.EntityQuery.from('制造商').在哪里( ... ).toType('制造商');

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

我敢打赌这会立即对您有用(一旦您解决了下面描述的命名约定问题).

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

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

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

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

自定义 JsonResultsAdapter

查看 Breeze Edmunds 示例,其中 Breeze 客户端使用来自 Edmunds Vehicle Information 服务的数据.

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

<前>{makeHolder":[{"id":200347864,楷模":[{"link":"/api/vehicle/am-general/hummer","id":"AM_General_Hummer",名称":悍马"}],"name":"AM 将军","niceName":"amgeneral",制造商":空,属性组":{}},... 更多的 ...]}

那里也没有 $type.微风开发者做了什么?他写了一个 自定义 Breeze JsonResultsAdapter,它是在文件 app/jsonResultsAdapter.js 中.

虽然只有 40 行,但我不会在这里复制该文件.我希望您阅读 jsonResultsAdapter 文档,拉下 Edmunds 示例,自己阅读.

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

这是一个片段:

<前>>if (node.id && node.models) {//移动 'node.models' 链接,因此 'models' 可以是空数组node.modelLinks = node.models;node.models = [];返回{实体类型:制作"}}

此代码段中有三个活动:

  1. 识别节点的内容(if ...)
  2. 调整节点值(无论出于何种原因)
  3. 组合并返回元"对象结果.

专注于#3.这就是开发人员告诉 Breeze 的地方把这个节点变成一个 Make 实体.

结构类型匹配

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

Breeze 不会通过匹配类型结构来判断实体类型.我也不认为它应该......因为不同的类型通常共享相同的结构.例如:我有一个 StatusCodeProductCode 实体类型,它们都是 { id: int, name: string}.我们还有许多其他增强功能需要处理;处理类型歧义在我们的列表中并不重要.

命名约定

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

您的 configureBreezeManager 方法开始:

breeze.NamingConvention.camelCase.setAsDefault();

您已将默认命名约定从客户端和服务器相同"更改为pascalCase-on-client/CamelCase-on-server".

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

这是正确的做法吗?如果您的服务器需要 CamelCase 属性名称,那就是.但是,根据 JSON 负载中的属性名称,服务器也需要 CamelCase.客户端和服务器上的属性名称相同.如果微风发送带有 Name 属性值而不是 name 属性值的制造商,将会发生不好的事情.

保留微风默认设置,什么都不做"约定.不要覆盖它.从您的 configureBreezeManager 中删除该 pascalCase 约定行.

正在保存更改

我们一直在讨论查询结果.我们根本没有讨论如何将更改保存回服务器.

我确定您有自己的协议(类似于 ReST 的东西?)和序列化格式.那是完全不同的讨论.让我们不要在这个 Stack Overflow 问题中讨论这个问题.我只是提醒您,您可能很快就会对这个问题挠头.

I am using Breeze.js without the server side components and have the entities being created on the client side with the following code. Per Ward's request I have simplified everything and am including more information. My MetaDataStore Configuration function-

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 }
            }
        });
    }

My JSON response from my server

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

My config code from my datacontext (the whole setup minus the lack of server metadata is set up after John Papa's courses)

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;
    }

When the models are pulled down the data is there, but the data is not wrapped in ko.observables and the ko.observables/ko.computed in the init functions are not in the models that are passed out of the queries. How can I ensure the model's data is wrapped in ko.observables and the ko.computeds are added?

解决方案

This answer is as much a tutorial on analyzing the problem as answer to the question.

Step 1 - Simplify and Isolate

Let's simplify radically until we find the missing step. Let's start with the simplest entity type you have ... one with 2 to 5 properties max. Don't have one? Make one up. Cut down the Manufacturer to just "id" and "name". We're trying to get the mechanics down first.

You aren't using any breeze components on the server. Fine. Pick a server endpoint that delivers that test entity data. Have that endpoint deliver a JSON array with just one instance. Show us the JSON that are arriving on the client ... the entire JSON payload, exactly as it arrives on the wire. It should be brief; if it isn't, you haven't simplified enough.

THEN we can figure out whether you need a JsonResultsAdapter and what it should look like if you do.

Show us the exact sequence by which you populate the metadataStore with an EntityType, ctor, and initializer. Frankly, I'd rather have no ctor or initializer until we've got the first one working.

How do you make sure that the EntityManager you create is using that store? We need to see your configuration code and how you new-up the EntityManager and use it to query the endpoint.

If you follow my suggestion there won't be much code. Twenty lines maybe. And the JSON feed should be about 10 lines. If you can't hit these numbers, you haven't simplified enough.

Step 2 - Review the simpler example

Now that you've re-worked the example, I have a better idea where to look.

Two things leap out at me:

  1. The JSON result from the server
  2. The camelCase naming convention

JSON from the Server

Let's pretty print the JSON results you provided and discuss them:

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

Breeze won't know what to do with that JSON object because it lacks type information. To Breeze its just an arbitrary object, perhaps the result of a projection.

Compare that to the JSON result of a query hitting the DocCode Web API. Here's the URL generated for the query:

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

and here's the (abbreviated) JSON result

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

By default a Breeze client expects data that have been serialized with JSON.NET, the default serializer in ASP.NET.

A JSON.NET payload is either a node or an array of nodes. JSON.NET adds its own $id and $type properties to each node.

I want to focus your attention on the $type property which you may recognize as the full name (class-with-namespace, assembly-name) of a .NET type.

You could get away without the $id property.

The $id is an auto-incrementing serialization key. Often the same object appears multiple times in a payload. Instead of repeating the contents, JSON.NET substitutes a simple node like {$ref: #} where # refers to the $id of an earlier node. This approach both reduces the payload size and breaks circular references.

But Breeze is really looking forward to that $type property. That is how it connects the JSON object/node to a type in your metadata. If your manufacturer example node had one, it might like this:

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

I don't know how you're serializing data on your server. It would seem you are using something other than JSON.NET.

That is cool. I'm just telling you how Breeze works by default; it is very .NET friendly. But Breeze doesn't need .NET. It is a pure JavaScript library. You just have to tell it what you want.

Use toType(...)

The simplest thing you can do is add toType to your query.

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

In this way, you state explicitly that the top level node(s) returned by the 'Manufacturers' endpoint contain data for the Manufacturer type that you described in metadata.

I'll bet this works for you right away (once you fix the Naming Convention problem described below).

This is an effective approach but it has several drawbacks. I'll mention two:

  1. You have to remember to add it to every query.

  2. It only works for top-level entities; if won't work for nested entities such as are returned when you apply the .expand() clause.

I prefer to teach the Breeze client how to interpret the JSON results on its own ... with a custom JsonResultsAdapter.

Custom JsonResultsAdapter

Check out the Breeze Edmunds Sample in which a Breeze client consumes data from the Edmunds Vehicle Information service.

The Edmunds server sends a completely different kind of JSON payload in response to queries. Here is a snippet:

{
   "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 ...
   ]
}

No $type there either. What did the Breeze developer do? He wrote a custom Breeze JsonResultsAdapter and it's in the file app/jsonResultsAdapter.js.

I'm not going to reproduce that file here although it is only 40 lines. I want you to read the jsonResultsAdapter documentation, pull down the Edmunds sample, and read it for yourself.

I will summarize what it does and how it works. Breeze calls your jsonResultsAdapter first when it receives a JSON payload and again as it processes each node in that payload. Your job is tell Breeze how to treat that node which you do by tweaking the node itself and returning a meta object that describes the node.

Here's a snippet:

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

There are three activities in this snippet:

  1. identifying what the node is about (the if ...)
  2. adjusting the node values (for whatever reasons make sense to you)
  3. composing and returning the "meta" object result.

Focus on #3. That's where the developer told Breeze "Turn this node into a Make entity.

Structural type matching

You might say, "Hey Ward, the Manufacturer entity type matches the JSON object structure exactly. Breeze should recognize it as a Manufacturer."

Breeze does not divine the entity type by matching the type structure. Nor do I think it should ... because different types often share the same structure. For example: I have a StatusCode and ProductCode entity types that are both { id: int, name: string}. We have plenty of other enhancements to work on; coping with type ambiguity is not high on our list.

Naming Convention

Finally, let's return to the other problem that I saw.

Your configureBreezeManager method begins:

breeze.NamingConvention.camelCase.setAsDefault();

You've changed the default Naming Convention from "same-on-client-and-server" to "pascalCase-on-client/CamelCase-on-server".

By switching to the camelCase convention, you're telling Breeze that a client-side property foo should be sent to the server as Foo.

Is that the right thing to do? It would be if your server expected CamelCase property names. But, based on the property names in your JSON payload, the server expects CamelCase too. The property names are identical on client and server. Bad things will happen if breeze sends a manufacturer with a Name property value instead of a name property value.

Leave the breeze default, "do nothing" convention in place. Don't override it. Remove that pascalCase convention line from your configureBreezeManager.

Saving changes

We've been talking about query results. We haven't talked at all about how you're going to save changes back to the server.

I'm sure you have your own protocol (something ReST-like?) and serialization format. That is a completely different discussion. Let's not get into that in this Stack Overflow question. I'm just alerting you to the probability that you'll be scratching your head about this one pretty soon.

这篇关于为什么我的 Breeze.js 实体没有创建 ko.observables?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆