(Обновлено для API-интерфейсов Ember-Data Rev 11...)
TL; DR
Каков правильный способ использования DS.Adapter.findAssociation(...)
DS.Adapter.findHasMany(...)
для загрузки ассоциаций hasMany
по запросу? Особенно, когда вы загружаете дочерние записи, как вы справляетесь с тем, что перезагрузка родительской записи с сервера опустошает массив hasMany? Я не хочу (возможно, не могу) включать id дочерних записей в массив в родительском. Или есть другой способ сделать это, чтобы я отсутствовал?
В качестве побочного примечания я смущен тем, какие параметры должны быть переданы в определении hasMany
/belongsTo
для привязки ключей (и я должен использовать сопоставления, если у меня нет данных с боковой загрузкой или массива идентификаторов?), поэтому, если вы считаете, что моя проблема может заключаться в определении моей ассоциации, есть хороший шанс, что вы правы.
Длинная версия
Я пишу свой собственный подкласс DS.RESTAdapter
для привязки данных ember к бэкэнду ASP.NET WebAPI (используя Entity Framework). До сих пор так хорошо, но у меня есть время, чтобы ассоциации работали правильно.
Подобно этому плакату, я заметил, что на передней панели говорится, что если у вас есть ассоциация hasMany
в вашей модели, и вы get
это свойство, магазин выдает запрос для дочерних записей. Цитата со страницы:
Если вы хотите запросить профиль, выполните следующие действия:
author.get('profile');
... адаптер REST отправит запрос URL/профили? author_id = 1.
Импликация - это то, что происходит, если вы не босиком и не включаете массив идентификаторов. Я понимаю, что эти документы несколько устарели, но я не смог это сделать, либо в версии API версии 7, либо в более поздней версии 9. Однако в версии 9 я нашел метод в версии 11 существует метод findAssociation
findHasMany
, который, как я предполагаю, может быть использован для этого, и который я сейчас пытаюсь использовать.
Почему бы не включить массив идентификаторов или боковой нагрузки?
Три основные причины, по которым я не хочу этого делать (и, возможно, не могу):
-
Не очевидно, как сделать любую из этих вещей с помощью ASP.NET WebAPI, по крайней мере, не с помощью простого подхода, основанного на декорировании, который я использую. И мне очень нравится простота и тонкость бэкэнда прямо сейчас, когда EF и WebAPI почти полностью настраиваются для каждого объекта, и я закончил! Я даже получаю поддержку фильтрации OData "бесплатно".
-
Мои дочерние записи часто генерируются с помощью дорогостоящих запросов (например, сводки агрегатов... показателей). И существует множество разных классов дочерних объектов для одного родительского объекта. Таким образом, даже получение идентификаторов для всех дочерних типов будет дорогостоящим, и вопрос о создании и загрузке всех дочерних записей не может быть и речи.
-
У меня есть дочерние объекты, где первичный ключ является составным. Я не видел пример того, что это даже поддерживается/возможно в данных ember-данных, по крайней мере, не для работы с ассоциациями (например, как бы вы сделали массив идентификаторов?). Я сделал вычисляемое свойство в моей клиентской модели, которая объединяет составной ключ в одну строку, поэтому я могу получить одну запись из хранилища с помощью
find(...)
, но опять же я понятия не имею, как это будет работать с ассоциацией.
Попытка использовать findAssociation
findHasMany
findAssociation
Я понял, что в версии 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.
}
});
}
Чтобы сделать эту работу, мои определения hasMany
устанавливают значение параметра query
, которое является функцией в записи, которая возвращает хэш параметров строки запроса в запросе для детей. Поскольку это для бэкэнд ASPAP WebAPI, это, вероятно, будет фильтром OData, например:
App.ParentEntity = DS.Model.extend({
...
children: DS.hasMany('App.ChildEntity', {
query: function (record) {
return {
"$filter": "ChildForeignKey eq '" + record.get('id') + "'"
};
}
})
});
Один из трюков заключается в добавлении элементов в ManyArray
с помощью addToContent(item)
, так что родительская запись не будет отмечена как "грязная", как если бы она была отредактирована. Другое, когда я сначала извлекаю 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, 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
, вы увидите, что передается для второго параметра:
relationship = store.findMany(type, ids || [], this, meta);
Таким образом, единственный способ получить findAssociation
findHasMany
- это присвоить значение в JSON что-то, что является "правдивым", но не является массивом - я используйте true
. Я думаю, что это либо ошибка/неполная, либо указание на то, что я ошибаюсь - если кто-то может сказать мне, что это тоже здорово.
При этом я могу получить данные ember-data, чтобы автоматически выдавать запрос на сервер для дочерних записей, например. до http://myserver.net/api/child_entity/$filter=ChildForeignKey eq '42'
, и он работает - дочерние записи загружаются, и они связаны с родительской записью (кстати, обратная связь belongsTo
также заполняется должным образом, несмотря на то, что я явно не прикасаюсь к ней) Я понятия не имею, где и как это происходит).
Но это довольно быстро ломается, если я не останавливаюсь там.
Работа с перезагрузкой родительской записи
Итак, скажите, что я успешно загрузил дочерние записи в родительскую запись, но затем перейдите к месту, где извлекаются все родительские записи (чтобы заполнить меню). Поскольку у недавно загруженных родительских записей нет массива идентификаторов и ничего не загружено, родительская запись обновляется без каких-либо детей снова! Хуже того, свойство ManyArray
isLoaded
остается true
! Поэтому я не могу ничего наблюдать за повторной загрузкой детей.
Итак, если у меня одновременно есть вид на экране, отображающий дочерние значения, он сразу же не имеет никаких дочерних записей. Или, если я вернусь к одному, когда вызывается App.store.find('App.ParentEntity', 42)
, запись загружается из хранилища без запроса на сервер и, конечно же, у него нет дочерних записей.
Это намек №2, что, вероятно, я ошибаюсь. Итак... Каков правильный способ загрузки дочерних записей по запросу?
Спасибо большое!