TL; DR
什么是使用DS.Adapter.findAssociation(…)DS.Adapter.findHasMany(…)正确的方式来加载hasMany关联按需?特别是,一旦加载子记录,您如何处理从服务器重新加载父记录清空hasMany数组的事实?我不想(也许不能)将父记录中的子记录的id包含在数组中。还是有另一种方法来做到这一点我失踪了?
作为附注,我感到困惑的是在一个hasMany / belongsTo定义中应该传递什么选项来进行关键连接(如果我没有加载数据或ids数组,我应该使用映射),所以如果你认为我的问题可能在于我的关联定义,很有可能你是对的。
长版
我正在编写自己的DS.RESTAdapter子类,将ember数据与ASP.NET WebAPI后端(使用实体框架)相结合。到目前为止这么好,但是我有一段时间让协会正常工作。
与this poster类似,我注意到,ember数据的首页表示曾经说过,如果您的模型中有一个hasMany关联,并且获得该属性,那么该商店将发出对该子记录的请求。从页面引用:
If you were to request the profile,like this:
06000
…the REST adapter would send a request to the URL /profiles?author_id=1.
这意味着,如果您不负载并且不包括ids数组,会发生什么。我意识到这些文档有些过时,但是我无法在API版本7或更新版本9中发生这种情况。但是在版本9中,我找到了findAssociation方法,在版本11中有findHasMany方法,我猜这是可能已经被用来做这个事情,我正在试图使用。
为什么不包括ids或sideload的数组?
三个主要原因我不想这样做(可能不能):
> ASP.NET WebAPI中的任何一项都不明显,至少没有使用简单的基于装饰的方法。而且,我真的很喜欢后端的简单性和薄度,EF和WebAPI几乎完全是每个实体的样板,我已经完成了!我甚至得到OData过滤支持“免费”。
>我的子记录通常会通过昂贵的查询生成(例如,聚合…指标汇总)。对于单个父实体,还有很多不同类的子实体。所以即使得到所有的孩子类型的ids将是昂贵的,生成和侧面加载所有的子记录是不成问题的。
>我有一个子实体,其中主键是一个复合键。我甚至没有看到一个这样的例子,即使在ember数据中被支持/可能,至少不是为了处理关联(例如,你将如何做一个ids数组)?我在客户端模型中创建了一个计算属性,将复合键强制为单个字符串,因此我可以使用find(…)从商店检索单个记录,但是我也不知道这甚至会如何工作与协会。
试图使用findAssociationfindHasMany
我已经在API版本9(和一些较早的版本,但不是全部?)11中,我可以实现DS.Adapter.findAssociation DS.Adapter.findHasMany方法来检索hasMany关联的子记录。这主要是工作,但需要一些体操。这是我的广义findAssociation findHasMany方法:
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. } }); }
为了使这个工作,我的许多定义设置了一个查询选项值,它是一个记录上的函数,该函数返回对该请求中的查询字符串的参数的哈希值。由于这是一个ASP.NET WebAPI后端,这可能是一个OData过滤器,例如:
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根本没有密钥)。即:
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在哪里被调用的位置,你会发现:
findMany: function(type,ids,relationship){ ... if (!Ember.isArray(ids)) { var adapter = this.adapterForType(type); if (adapter && adapter.findHasMany) { adapter.findHasMany(this,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的这一行,您将看到第二个参数传递的内容:
relationship = store.findMany(type,ids || [],this,Meta);
所以,找到FindAsHouseMany被调用的唯一方法是使JSON中的值是“真实的”,但不是一个数组 – 我使用true。我认为这是一个错误/不完整,或者表明我在错误的轨道上 – 如果有人能告诉我哪个也是伟大的。
有了这些,我可以得到余烬数据自动向服务器发出一个请求给子记录,例如到http://myserver.net/api/child_entity/$filter=ChildForeignKey eq’42’,它的工作原理 – 子记录被加载,并且它们与父记录相关联(顺便说一下,inverse belongsTo关系被正确地填充尽管事实上我并没有明确地触及它 – 我不知道在哪里或发生了什么。
但如果我不停在那里,那很快就会崩溃。
处理重新加载父记录
所以说我成功地将子记录加载到父记录中,然后导航到检索所有父记录的位置(以填充菜单)。由于新加载的父记录没有ids的数组,没有任何东西被加载,所以父记录不再有任何孩子被刷新!更糟糕的是,ManyArray的Lloaded属性依然是真实的!所以我甚至不能观察任何东西来重新加载孩子们。
所以如果我同时有一个屏幕上的显示子值的视图,它会立即变为没有子记录值。或者如果我导航回到一个,当App.store.find(‘App.ParentEntity’,42)被调用时,记录从商店加载而不需要服务器,当然它没有子记录。
这是提示#2,我可能会这样做错了。那么…根据需要装载子记录的正确方法是什么?
非常感谢!
解决方法
我在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'),relationship; if (typeof type === 'string') { type = get(this,type,false) || get(Ember.lookup,type); } Meta.key = key; ids = data[key]; relationship = store.findMany(type,Meta); set(relationship,'owner',this); set(relationship,'name',key); return relationship; }).property().Meta(Meta); })(type,options); };
首先,我添加了元对象的关键字…
Meta.key = key;
…其次,如上所述,我通过更改…从findMany调用中删除了空数组
relationship = store.findMany(type,Meta);
…至…
relationship = store.findMany(type,Meta);
…允许ids被传递到findMany作为未定义。
接下来,我添加了一个didFindHasMany钩子到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,payload,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,type); } });
我在DS.Adapter的didFindQuery钩子使用我发现已经在DS.Store上实现的loadHasMany之后建模。然后在我的自定义适配器中,我实现了一个findHasMany方法,它在其成功回调中使用以下代码:
Ember.run(this,function() { adapter.didFindHasMany(store,response.data,key); });
我没有广泛测试,但似乎正常工作。看看最近对ember-data代码的修改,在我看来,他们已经慢慢地朝着与未来某个时刻支持类似于这种方法的方向发展。或者至少这是我的希望。