17

(ember-data API Rev 11用に更新されました...)

TL; DR

アソシエーションをオンデマンドでロードするために使用する正しい方法は何ですか?特に、子レコードをロードしたら、サーバーから親レコードを再ロードするとhasMany配列が空になるという事実にどのように対処しますか?親の配列に子レコードのIDを含めたくない(おそらく含めることができない)。または、私が見逃しているこれを行う別の方法はありますか?DS.Adapter.findAssociation(...)DS.Adapter.findHasMany(...)hasMany

hasManyちなみに、キーリンケージの/定義でどのオプションを渡す必要があるかについて混乱していますbelongsTo(サイドロードされたデータやIDの配列がない場合は、マッピングを使用する必要がありますか?)。私の協会の定義にあります、あなたが正しい可能性が高いです。

ロングバージョン

DS.RESTAdapterember-dataをASP.NETWebAPIバックエンドに結び付けるための独自のサブクラスを作成しています(Entity Frameworkを使用)。これまでのところ順調ですが、アソシエーションを正しく機能させるのにかなりの時間がかかっています。

このポスターと同様に、ember-dataのフロントページに 、モデルに関連付けがあり、そのプロパティがある場合、ストアが子レコードのリクエストを発行すると書かていることに気付きました。ページからの引用:hasManyget

次のようにプロファイルをリクエストする場合:

author.get('profile');

…RESTアダプターはURL/profiles?author_id=1にリクエストを送信します。

これは、サイドローディングを行わず、IDの配列を含めない場合に発生することを意味します。これらのドキュメントはやや古くなっていることに気付きましたが、APIバージョン7または最近のバージョン9のいずれでも、これを実現できませんでした。ただし、バージョン9ではメソッドを見つけましたがfindAssociation、バージョン11ではfindHasMany私が推測しているメソッドは、これを実現するために使用された可能性があり、現在使用しようとしています。

IDまたはサイドロードの配列を含めてみませんか?

私がこれをしたくない(そしておそらくできない)3つの主な理由:

  1. 少なくとも私が使用している単純な装飾ベースのアプローチでは、ASP.NETWebAPIを使用してこれらのいずれかを実行する方法は明らかではありません。そして、私は今、バックエンドのシンプルさと薄さを本当に気に入っています。EFとWebAPIを使用すると、各エンティティのほぼ完全な定型文になり、完了です。ODataフィルタリングのサポートも「無料」で利用できます。

  2. 私の子レコードは、多くの場合、高価なクエリ(たとえば、集計...メトリックのロールアップ)を介して生成されます。また、単一の親エンティティには、さまざまなクラスの子エンティティがあります。したがって、すべての子タイプのIDを取得することでさえコストがかかり、すべての子レコードを生成してサイドローディングすることは問題外です。

  3. 主キーが複合キーである子エンティティがあります。少なくともアソシエーションを処理するためではなく、ember-dataでサポート/可能でさえあるこの例を見たことがありません(たとえば、IDの配列をどのように実行しますか?)。クライアント側モデルで、複合キーを単一の文字列に強制する計算プロパティを作成したので、を使用してストアから単一のレコードを取得できますが、find(...)これが関連付けでどのように機能するかはわかりません。

使用しようとしていますfindAssociationfindHasMany

APIバージョン9(および以前のバージョンの一部ですがすべてではありませんか?) 11では、関連付けの子レコードを取得するメソッドを実装できる可能性があることがわかりました。これはほとんど機能しますが、体操が必要です。これが私の一般化された方法です:DS.Adapter.findAssociation DS.Adapter.findHasManyhasManyfindAssociation 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では、子のリクエストでクエリ文字列のパラメータのハッシュを返すレコード上の関数であるオプション値を設定します。これはASP.NETWebAPIバックエンド用であるため、これはおそらくODataフィルターになります。例:

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

ManyArray秘訣の1つは、 usingにアイテムを追加しaddToContent(item)て、親レコードが編集されたかのように「ダーティ」とマークされないようにすることです。もう1つは、最初に親レコードの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内部関数からこの行を見ると、2番目のパラメーターに何が渡されているかがわかります。hasAssociation hasRelationshipfindMany

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

したがって、呼び出される唯一の方法は、JSONの値を「真実」であるが、配列ではないものにすることです。私はを使用します。これはバグ/不完全であるか、私が間違った方向に進んでいることを示していると思います-誰かがそれも素晴らしいと私に言うことができれば。findAssociation findHasManytrue

これで、ember-dataを取得して、子レコードのリクエストをサーバーに自動的に発行できます。たとえばhttp://myserver.net/api/child_entity/$filter=ChildForeignKey eq '42'、子レコードが読み込まれ、親レコードに関連付けられます(ちなみに、逆のbelongsTo関係)。明示的に触れていないにもかかわらず、適切に入力されます。どこで、どのように発生しているのかわかりません)。

しかし、私がそこで止まらなければ、それはかなりすぐに崩壊します。

親レコードのリロードの処理

つまり、子レコードを親レコードに正常にロードした後、すべての親レコードが取得される場所に移動するとします(メニューにデータを入力するため)。新しくロードされた親レコードにはIDの配列がなく、サイドロードも行われないため、親レコードは子なしで更新されます。さらに悪いことに、ManyArray'sisLoadedプロパティは残りtrueます!だから私は子供たちをリロードするために何も観察することさえできません。

したがって、子の値を表示するビューを同時に画面に表示すると、子のレコード値がないことにすぐになります。または、1つに戻ると、App.store.find('App.ParentEntity', 42)が呼び出されたときに、サーバーへの要求なしでレコードがストアからロードされ、もちろん子レコードはありません。

これは、私がおそらくこれを間違った方法で行っているというヒント#2です。それで...オンデマンドで子レコードをロードする正しい方法は何ですか?

どうもありがとう!

4

1 に答える 1

10

最新の Ember Data (2013 年 1 月 25 日現在) に基づいて...これが hasMany 関係の遅延読み込みに対する私の解決策です。にメソッドを修正DS.hasMany・追加しましたDS.Adapter

の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'),
          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オブジェクトに を追加しました...

meta.key = key;

...そして2番目に、前述のように、findMany呼び出しから空の配列を削除して...

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

...に...

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

... asidsに渡すことができます。findManyundefined

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, 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フックをモデルにしました。次に、カスタム アダプターで、成功のコールバックで次のコードを使用するメソッドを実装しました。loadHasManyDS.StorefindHasMany

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

私はこれを広範囲にテストしていませんが、正しく動作しているようです。ember-data のコードに加えられた最近の変更を見てみると、このアプローチに似たものが将来のある時点でサポートされる方向にゆっくりと動いているように思えます。または、少なくともそれが私の希望です。

于 2013-01-26T01:24:19.570 に答える