asp.net-web-api ember.js ember-data

asp.net web api - ember-data: Cargando hasMany asociación a pedido



asp.net-web-api ember.js (1)

(Actualizado para la API de datos de noviembre Rev 11 ...)

TL; DR

¿Cuál es la forma correcta de usar DS.Adapter.findAssociation(...) DS.Adapter.findHasMany(...) para cargar hasMany asociaciones de hasMany a pedido? Especialmente, una vez que carga registros secundarios, ¿cómo lidia con el hecho de que al volver a cargar el registro principal del servidor vacía la matriz hasMany? No quiero (quizás no) incluir los ID de los registros secundarios en una matriz en el padre. ¿O hay otra forma de hacer esto que me estoy perdiendo?

Como nota al margen, estoy confundido acerca de qué opciones deben pasarse en una definición de hasMany / belongsTo para el enlace clave (¿y se supone que debo usar asignaciones si no tengo datos cargados o un conjunto de identificadores?), Así que si crees que mi El problema podría estar en la definición de mi asociación, es muy probable que tenga razón.

Versión larga

Estoy escribiendo mi propia subclase de DS.RESTAdapter para vincular los datos de miembros a un backend ASP.NET WebAPI (utilizando Entity Framework). Hasta ahora todo bien, pero me está costando mucho conseguir que las asociaciones funcionen bien.

Al igual que en este póster , noté que la primera página de ember-data dice que si tiene una asociación hasMany en su modelo y get esa propiedad, la tienda emitirá una solicitud para los registros secundarios. Citando desde la página:

Si tuvieras que solicitar el perfil, así:

author.get(''profile'');

… El adaptador REST enviaría una solicitud a la URL / perfiles? Author_id = 1.

La implicación es que esto es lo que sucede si no se descarga y no incluye una serie de ID. Me doy cuenta de que estos documentos están un poco desactualizados, pero no he podido hacer que esto suceda, ya sea en la versión 7 de API o más recientemente en la versión 9. Sin embargo, en la versión 9 encontré el método findAssociation , en la versión 11 hay findHasMany método findHasMany , que supongo, es lo que podría haberse usado para hacer que esto suceda, y que ahora estoy tratando de usar.

¿Por qué no incluir una matriz de identificadores o de carga lateral?

Tres razones principales por las que no quiero hacer esto (y posiblemente no puedo):

  1. No es obvio cómo hacer cualquiera de estas cosas con ASP.NET WebAPI, al menos no con el enfoque simple basado en la decoración que estoy usando. Y, realmente me gusta la simplicidad y la delgadez del backend en este momento, con EF y WebAPI es casi completamente repetitivo para cada entidad y ¡ya terminé! Incluso tengo el soporte de filtrado OData "gratis".

  2. Los registros de mis hijos a menudo se generan a través de consultas costosas (agregados ... acumulaciones de métricas, por ejemplo) Y hay muchas clases diferentes de entidades secundarias para una entidad matriz única. Por lo tanto, incluso obtener los identificadores para todos los tipos secundarios sería costoso, y generar y descargar todos los registros secundarios está fuera de discusión.

  3. Tengo entidades secundarias en las que la clave principal es una clave compuesta. No he visto un ejemplo de esto, incluso sea compatible / posible en datos de brasas, al menos no para tratar con asociaciones (por ejemplo, ¿cómo haría una matriz de identificadores?). Hice una propiedad computada en mi modelo del lado del cliente que convierte la clave compuesta en una sola cadena, por lo que puedo recuperar un solo registro de la tienda usando find(...) , pero nuevamente no tengo idea de cómo funcionaría esto. con una asociación.

Tratando de usar findAssociation findHasMany

Descubrí que en la versión 9 de la API (¿y en algunas versiones anteriores pero no en todas?) 11, quizás pueda implementar el método DS.Adapter.findAssociation DS.Adapter.findHasMany para recuperar los registros secundarios de una asociación hasMany . Esto funciona principalmente, pero requiere algo de gimnasia. Aquí está mi método general 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. } }); }

Para hacer que esto funcione, mis definiciones de hasMany establecen un valor de opción de query que es una función en el registro que devuelve un hash de parámetros para la cadena de consulta en la solicitud para los hijos. Dado que esto es para un backend ASP.NET WebAPI, probablemente será un filtro OData, por ejemplo:

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

Uno de los trucos es agregar los elementos a ManyArray usando addToContent(item) , para que el registro principal no se marque como "sucio" como si se hubiera editado. La otra es que, cuando recupero el JSON para el registro principal inicialmente, tengo que establecer manualmente un valor de true para la clave del nombre de la asociación (el JSON que viene del servidor no tiene la clave). Es decir:

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

Esto suena loco, pero esta es la razón: si observa la implementación de DS.Store.findMany y encuentra dónde se encuentra findAssociation findHasMany , encontrará:

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

Y si observa esta línea desde la función interna de ember-data hasAssociation hasRelationship donde se llama a findMany , verá lo que se pasa para el segundo parámetro:

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

Por lo tanto, la única forma de obtener findAssociation findHasMany es que el valor en el JSON sea algo que sea "sincero", pero que no sea un Array; uso true . Estoy pensando que esto es un error / incompleto, o una indicación de que estoy en el camino equivocado, si alguien pudiera decirme cuál sería también lo mejor.

Con todo eso, puedo conseguir que los datos de abeto emitan automáticamente una solicitud al servidor para los registros secundarios, por ejemplo, a http://myserver.net/api/child_entity/$filter=ChildForeignKey eq ''42'' y funciona ... los registros secundarios se cargan, y se asocian con el registro principal (por cierto, la relación de belongsTo inversa belongsTo se rellena correctamente, a pesar de que no lo estoy tocando explícitamente, no tengo idea de dónde o cómo está sucediendo eso) .

Pero eso se rompe bastante rápido si no me detengo allí.

Tratando de recargar el registro padre

Así que diga que cargué con éxito los registros secundarios en un registro principal, pero luego navego a un lugar donde se recuperan todos los registros principales (para rellenar un menú). Dado que los registros primarios recién cargados no tienen una matriz de ID y no se transfiere nada, ¡el registro principal se actualiza sin ningún elemento secundario de nuevo! Peor aún, la propiedad isLoaded sigue siendo true . Así que ni siquiera puedo observar nada para recargar a los niños.

Por lo tanto, si simultáneamente tengo una vista en pantalla que muestra los valores secundarios, inmediatamente deja de tener valores de registro no secundarios. O si vuelvo a uno, cuando se App.store.find(''App.ParentEntity'', 42) , el registro se carga desde la tienda sin una solicitud al servidor y, por supuesto, no tiene registros secundarios.

Esta es la sugerencia # 2 de que probablemente voy a hacer esto por el camino equivocado. Entonces ... ¿cuál es la forma correcta de cargar registros secundarios a pedido?

¡Muchas gracias!


Basado en los últimos datos de Ember (hasta el 25 de enero de 2013) ... aquí está mi solución para la carga perezosa de muchas relaciones. Modifiqué DS.hasMany y agregué un método a DS.Adapter .

He cambiado dos líneas en 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); };

Primero, agregué la key al meta objeto ...

meta.key = key;

... y segundo, como se indicó anteriormente, findMany la matriz vacía de la llamada a findMany cambiando ...

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

...a...

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

... permitiendo que los findMany se pasen a findMany como undefined .

A continuación, agregué un gancho DS.Adapter a 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.Adapter esto después del gancho didFindQuery del didFindQuery usando el loadHasMany que encontré ya implementado en el DS.Store . Luego, en mi adaptador personalizado implementé un método findHasMany que usa el siguiente código en su devolución de llamada exitosa:

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

No he probado esto extensivamente pero parece estar funcionando correctamente. Mirando a través de las modificaciones recientes hechas al código de datos de abril, me parece que se han estado moviendo lentamente en una dirección en la que se apoyará algo similar a este enfoque en algún momento en el futuro. O al menos esa es mi esperanza.