ember-data:按需加载 hasMany 关联 [英] ember-data: Loading hasMany association on demand

查看:19
本文介绍了ember-data:按需加载 hasMany 关联的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(为 ember-data API Rev 11 更新...)

(Updated for ember-data API Rev 11...)

DS.Adapter.findAssociation(...)DS.Adapter.findHasMany(...) 按需加载 hasMany 关联?特别是,一旦你加载了子记录,你如何处理从服务器重新加载父记录会清空 hasMany 数组的事实?我不想想要(也许不能)将子记录的 id 包含在父数组中.还是有另一种方法可以做到这一点,我想念?

What is the right way to use DS.Adapter.findAssociation(...)DS.Adapter.findHasMany(...) to load hasMany associations on demand? Especially, once you load child records, how do you deal with the fact that re-loading the parent record from the server empties the hasMany array? I do not want to (perhaps can't) include the id's of the child records in an array in the parent. Or is there another way to do this that I'm missing?

作为旁注,我很困惑应该在 hasMany/belongsTo 定义中传递哪些选项以进行键链接(如果我没有旁加载数据或 ID 数组?),所以如果您认为我的问题可能出在我的关联定义中,那么您很有可能是对的.

As a side note, I'm confused about what options should be passed in a hasMany/belongsTo definition for key linkage (and am I supposed to use mappings if I have no sideloaded data or array of ids?), so if you think my problem could lie in my association definition, there's a good chance you're right.

我正在编写自己的 DS.RESTAdapter 子类以将 ember-data 绑定到 ASP.NET WebAPI 后端(使用实体框架).到目前为止一切顺利,但我有很长的时间让协会正常工作.

I am writing my own subclass of DS.RESTAdapter to tie ember-data to an ASP.NET WebAPI backend (using Entity Framework). So far so good, but I'm having a heck of a time getting associations to work right.

类似于这张海报,我注意到余烬-data 的首页 says used 表示如果您的模型中有 hasMany 关联,并且您 get该属性,存储将发出对子记录的请求.引自页面:

Similar to this poster, I noticed that ember-data's front page says used to say that if you have a hasMany association in your model, and you get that property, the store will issue a request for the child records. Quoting from the page:

如果您要请求个人资料,如下所示:

If you were to request the profile, like this:

author.get('profile');

……REST 适配器会向 URL/profiles?author_id=1 发送请求.

…the REST adapter would send a request to the URL /profiles?author_id=1.

这意味着如果您不进行旁加载并且不包含一组 id,就会发生这种情况.我意识到这些文档有些过时了,但是我无法在 API 版本 7 或最近的版本 9 中实现这一点.但是 在版本 9 中我确实找到了 findAssociation 方法, 在版本 11 中有 findHasMany 方法,我猜可能是用它来实现这一点,我现在正在尝试使用.

The implication is this is what happens if you don't sideload and don't include an array of ids. I realize that these docs are somewhat out of date, but I haven't been able to make this happen, either in API version 7 or more recently version 9. However in version 9 I did find the findAssociation method, in version 11 there is the findHasMany method, which I'm guessing is what might have been used to make this happen, and which I'm now trying to use.

我不想这样做(并且可能不能)的三个主要原因:

Three main reasons I don't want to do this (and possibly can't):

  1. 如何使用 ASP.NET WebAPI 执行这些操作并不明显,至少我使用的基于装饰的简单方法不是.而且,我现在真的很喜欢后端的简单和纤薄,使用 EF 和 WebAPI,它几乎完全是每个实体的样板,我已经完成了!我什至免费"获得 OData 过滤支持.

  1. It's not obvious how to do either of these things with ASP.NET WebAPI, at least not with the simple decoration-based approach I'm using. And, I really like the simplicity and thinness of the backend right now, with EF and WebAPI it's almost entirely boilerplate for each entity and I'm done! I even get OData filtering support "free".

我的子记录通常是通过昂贵的查询(例如聚合...指标汇总)生成的.对于单个父实体,有许多不同类别的子实体.因此,即使获取所有子类型的 id 也会很昂贵,生成和旁加载所有子记录也是不可能的.

My child records will often be generated via expensive queries (aggregates...metrics rollups, for instance). And there are lots of different classes of child entities for a single parent entity. So even getting the ids for all the child types would be expensive, and generating and sideloading all the child records are out of the question.

我有主键是复合键的子实体.我还没有看到在 ember-data 中支持/可能的例子,至少不是用于处理关联(例如,您将如何处理一组 id?).我在客户端模型中创建了一个计算属性,该属性将复合键强制转换为单个字符串,因此我可以使用 find(...) 从存储中检索单个记录,但我再次有不知道这甚至如何与关联一起工作.

I have child entities where the primary key is a composite key. I haven't seen an example of this even being supported/possible in ember-data, at least not for dealing with associations (e.g. how would you do an array of ids?). I made a computed property in my client-side model that coerces the composite key into a single string, so I can retrieve a single record from the store using find(...), but again I have no idea how this would even work with an association.

尝试使用 findAssociationfindHasMany

我发现在 API 版本 9(以及一些早期版本但不是全部?) 11 中,我也许可以实现 DS.Adapter.findAssociation DS.Adapter.findHasMany 方法来检索 hasMany 关联的子记录.这大多有效,但需要一些体操.这是我通用的 findAssociation findHasMany 方法:

Trying to use findAssociationfindHasMany

I've figured out that in API version 9 (and some earlier versions but not all?) 11, I can perhaps implement the DS.Adapter.findAssociation DS.Adapter.findHasMany method to retrieve the child records of a hasMany association. This mostly works, but requires some gymnastics. Here is my generalized findAssociation findHasMany method:

findHasMany: function (store, record, relationship, ids) {

    var adapter = this;
    var root = this.rootForType(relationship.type);
    var query = relationship.options.query(record);

    var hits = store.findQuery(relationship.type, query);

    hits.on('didLoad', function () {
        // NOTE: This MUST happen in the callback, because findHasMany is in
        // the execution path for record.get(relationship.key)!!! Otherwise causes
        // infinite loop!!!
        var arrMany = record.get(relationship.key);

        if (hits.get('isLoaded')) {
            arrMany.loadingRecordsCount = 1 + hits.get('length') + (typeof arrMany.loadingRecordsCount == "number" ? arrMany.loadingRecordsCount : 0);
            hits.forEach(function (item, index, enumerable) {
                arrMany.addToContent(item);
                arrMany.loadedRecord();
            });
            arrMany.loadedRecord(); // weird, but this and the "1 +" above make sure isLoaded/didLoad fires even if there were zero results.
        }
    });

}

为了完成这项工作,我的 hasMany 定义设置了一个 query 选项值,它是记录上的一个函数,它返回请求中查询字符串的参数散列为了孩子.由于这是用于 ASP.NET WebAPI 后端,因此这可能是 OData 过滤器,例如:

To make this work, my hasMany definitions set a query option value which is a function on the record that returns a hash of parameters for the query string in the request for the children. Since this is for a ASP.NET WebAPI backend, this will probably be an OData filter, e.g.:

App.ParentEntity = DS.Model.extend({
    ...
    children: DS.hasMany('App.ChildEntity', {
        query: function (record) {
            return {
                "$filter": "ChildForeignKey eq '" + record.get('id') + "'"
            };
        }
    })
});

其中一个技巧是使用 addToContent(item) 将项目添加到 ManyArray,这样父记录就不会被标记为脏",就好像它已被编辑.另一个是,当我最初检索父记录的 JSON 时,我必须手动为关联名称的键设置 true 值(来自服务器的 JSON 根本没有键).即:

One of the tricks is adding the items to the ManyArray using addToContent(item), so that the parent record doesn't get marked "dirty" as if it has been edited. The other is, when I retrieve the JSON for the parent record initially, I have to manually set a value of true for the association name's key (the JSON coming from the server does not have the key at all). I.e.:

    var a = Ember.isArray(json) ? json : [json];
    for (var i = 0; i < a.length; i++) {
        type.eachAssociation(function (key) {
            var meta = type.metaForProperty(key);
            a[i][key] = true;
        });
    }

这听起来很疯狂,但这就是为什么:如果您查看 DS.Store.findMany 的实现并找到 findAssociation findHasMany 被调用,你会发现:

This sounds nuts, but this is why: If you look at the implementation of DS.Store.findMany and find where findAssociation findHasMany is called, you'll find:

findMany: function(type, ids, record, relationship){
...
if (!Ember.isArray(ids)) {
  var adapter = this.adapterForType(type);
  if (adapter && adapter.findHasMany) { adapter.findHasMany(this, record, relationship, ids); }
  else { throw fmt("Adapter is either null or does not implement `findMany` method", this); }

  return this.createManyArray(type, Ember.A());
}

如果你从 ember-data 内部函数中查看这一行 hasAssociation hasRelationship 其中 findMany 是调用,您将看到为第二个参数传递的内容:

And if you look at this line from the ember-data internal function hasAssociation hasRelationship where findMany is called, you'll see what is passed for the 2nd parameter:

relationship = store.findMany(type, ids || [], this, meta);

因此,让 findAssociation findHasMany 被调用的唯一方法是让 JSON 中的值是真实的"",但是不是一个数组——我使用true.我认为这要么是错误/不完整,要么表明我走错了路——如果有人能告诉我哪个也很棒.

So, the only way to get findAssociation findHasMany to be called is to have the value in the JSON be something that is "truthy", but is not an Array--I use true. I'm thinking this is either a bug/incomplete, or an indication that I'm on the wrong track--if someone could tell me which that would be great too.

有了这一切,我可以让 ember-data 自动向服务器发出请求以获取子记录,例如到 http://myserver.net/api/child_entity/$filter=ChildForeignKey eq '42' 并且它工作 - 子记录被加载,并且它们与父记录相关联(顺便说一句,反向 belongsTo 关系也被正确填充,尽管我没有明确地接触它——我不知道这是在哪里或如何发生的).

With all that, I can get ember-data to automatically issue a request to the server for the child records, e.g. to http://myserver.net/api/child_entity/$filter=ChildForeignKey eq '42' and it works--the child records get loaded, and they get associated with the parent record (incidentally, the inverse belongsTo relationship gets populated properly too, despite the fact that I'm not explicitly touching it--I have no idea where or how that is happening).

但如果我不就此止步,那很快就会崩溃.

But that breaks down pretty quickly if I don't stop there.

假设我成功地将子记录加载到父记录中,然后导航到检索所有父记录的位置(以填充菜单).由于新加载的父记录没有 id 数组,也没有侧载任何内容,因此父记录将再次刷新而没有任何子记录!更糟糕的是,ManyArrayisLoaded 属性仍然是 true!所以我什至不能观察任何东西来重新加载孩子们.

So say I successfully load up the child records into a parent record, but then navigate to a place where all the parent records are retrieved (to populate a menu). Since the newly loaded parent records have no array of ids and nothing is sideloaded, the parent record is refreshed without any children again! Worse, the ManyArray's isLoaded property remains true! So I can't even observe anything to re-load the children.

因此,如果我同时在屏幕上有一个显示子值的视图,它会立即切换到没有子记录值.或者,如果我导航回一个,当 App.store.find('App.ParentEntity', 42) 被调用时,记录从商店加载,而无需向服务器发出请求,当然它没有子记录.

So if I simultaneously have a view onscreen displaying the child values, it immediately zaps to having no child record values. Or if I navigate back to one, when App.store.find('App.ParentEntity', 42) is called, the record is loaded from the store without a request to the server, and of course it has no child records.

这是提示#2,我可能会以错误的方式处理此问题.那么...按需加载子记录的正确方法是什么?

This is hint #2 that I probably am going about this the wrong way. So...what is the right way to load child records on demand?

非常感谢!

推荐答案

基于最新的 Ember 数据(截至 2013 年 1 月 25 日)...这是我对延迟加载 hasMany 关系的解决方案.我修改了 DS.hasMany 并在 DS.Adapter 中添加了一个方法.

Based on the latest Ember Data (as of Jan 25th 2013)... here's my solution to lazy loading hasMany relationships. I modifed DS.hasMany and added a method to DS.Adapter.

我在 DS.hasMany 中更改了两行:

I changed two lines in DS.hasMany:

DS.hasMany = function(type, options) {
  Ember.assert("The type passed to DS.hasMany must be defined", !!type);
  return (function(type, options) {
    options = options || {};
    var meta = { type: type, isRelationship: true, options: options, kind: 'hasMany' };
    return Ember.computed(function(key, value) {
      var data = get(this, 'data').hasMany,
          store = get(this, 'store'),
          ids, relationship;

      if (typeof type === 'string') {
        type = get(this, type, false) || get(Ember.lookup, type);
      }

      meta.key = key;
      ids = data[key];
      relationship = store.findMany(type, ids, this, meta);
      set(relationship, 'owner', this);
      set(relationship, 'name', key);
      return relationship;
    }).property().meta(meta);
  })(type, options);

};

首先,我将 key 添加到 meta 对象...

First, I added the key to the meta object...

meta.key = key;

...第二,如前所述,我通过更改...从 findMany 调用中删除了空数组...

...and second, as previously noted above, I removed the empty array from the findMany call by changing...

relationship = store.findMany(type, ids || [], this, meta);

...到...

relationship = store.findMany(type, ids, this, meta);

...允许将 ids 作为 undefined 传递给 findMany.

...allowing ids to be passed in to findMany as undefined.

接下来,我向 DS.Adapter 添加了一个 didFindHasMany 钩子:

Next, I added a didFindHasMany hook to DS.Adapter:

DS.Adapter.reopen({

  /**
   Loads the response to a request for records by findHasMany.

   Your adapter should call this method from its `findHasMany`
   method with the response from the backend.

   @param {DS.Store} store
   @param {subclass of DS.Model} type
   @param {any} payload
   @param {subclass of DS.Model} record the record of which the relationship is a member (parent record)
   @param {String} key the property name of the relationship on the parent record
   */
  didFindHasMany: function(store, type, payload, record, key) {

    var loader = DS.loaderFor(store);

    loader.populateArray = function(references) {
      store.loadHasMany(record, key, references.map(function(reference) { return reference.id; }));
    };

    get(this, 'serializer').extractMany(loader, payload, type);
  }

});

我在 DS.AdapterdidFindQuery 钩子之后使用我发现已经在 DS 上实现的 loadHasMany 进行建模.存储.然后在我的自定义适配器中,我实现了一个 findHasMany 方法,该方法在其成功回调中使用以下代码:

I modeled this after the DS.Adapter's didFindQuery hook using the loadHasMany that I found already implemented on the DS.Store. Then in my custom adapter I implemented a findHasMany method that uses the following code in its success callback:

Ember.run(this, function() {
  adapter.didFindHasMany(store, type, response.data, record, key);
});

我没有对此进行过广泛的测试,但它似乎工作正常.查看最近对 ember-data 代码所做的修改,在我看来,他们正在慢慢地朝着在未来某个时候支持类似这种方法的方向发展.或者至少这是我的希望.

I haven't tested this extensively but it seems to be working correctly. Looking through recent modifications made to ember-data's code, it seems to me that they have slowly been moving in a direction in which something similar to this approach will be supported at some point in the future. Or at the very least that's my hope.

这篇关于ember-data:按需加载 hasMany 关联的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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