【问题标题】:ExtJS 4.1 - Returning Associated Data in Model.Save() ResponseExtJS 4.1 - 在 Model.Save() 响应中返回关联数据
【发布时间】:2013-08-19 08:15:24
【问题描述】:

我很好奇为什么 Model.save() 响应的结果集中包含的记录没有正确返回更新的关联数据,尽管更新的数据包含在服务器响应中...

示例模型和商店定义:

Ext.define("App.model.test.Parent",{
    extend: 'Ext.data.Model',
    requires: ['App.model.test.Child'],
    fields: [
            {name: 'id', type: 'int' },
            {name: 'name', type: 'string'},
            {name: 'kids', type: 'auto', defaultValue: []}
    ],
    idProperty: 'id',

    hasMany: [{
            foreignKey: 'parent_id',
            model: 'App.model.test.Child', 
            associationKey: 'kids',
            name: 'getKids'   
    }],

    proxy: {
        type: 'ajax',
        api : {
            create: '/service/test/create/format/json',
            read : '/service/test/read/format/json',
            update : '/service/test/update/format/json'
        },

        reader: {
            idProperty      : 'id',
            type            : 'json',
            root            : 'data',        
            successProperty : 'success',       
            messageProperty : 'message'
        },

        writer: {
            type            : 'json',
            writeAllFields  : true
        }
    }
});

Ext.define("App.model.test.Child",{
    extend: 'Ext.data.Model',
    fields: [
        {name: 'id', type: 'int' },
        {name: 'name', type: 'string'},
        {name: 'parent_id', type: 'int'}
    ]
});

Ext.define("App.store.test.Simpson",{
    storeId: 'TheSimpsons',
    extend: 'Ext.data.Store',
    model : 'App.model.test.Parent',
    autoLoad: true,
    autoSync: false
});

应用服务器对代理的READ 请求的响应使用单个模型及其相关数据。这一切都在起作用!

服务器对读取请求的响应

{
"data":{
    "id":1,
    "name":"Homer Simpson",
    "children":{
        "1":{
            "id":1,
            "name":"Bart Simpson"
        },
        "2":{
            "id":2,
            "name":"Lisa Simpson"
        },
        "3":{
            "id":3,
            "name":"Maggie Simpson"
        }
    }
},
"success":true,
"message":null
}

到目前为止,一切都在按计划进行......

store = Ext.create("App.store.test.Simpson");
homer = store.getById(1);
kids  = homer.getKids().getRange();
console.log("The Simpson Kids", kids);  // [>constructor, >constructor, >constructor]

不希望的行为始于保存和更新请求

这是我对 UPDATE 请求的测试响应...

/** Server UPDATE Response */
{
"data":{
    "id":1,
    "name":"SAVED Homer Simpson",
    "kids":[{
        "id":1,
        "name":"SAVED Bart Simpson",
        "parent_id":1
    },{
        "id":2,
        "name":"SAVED Lisa Simpson",
        "parent_id":1
    },{
        "id":3,
        "name":"SAVED Maggie Simpson",
        "parent_id":1
    }]
},
"success":true,
"message":null
}


/** Will call proxy UPDATE, response is above */
homer.save({
    success: function(rec, op){
        var savedRec = op.getRecords().pop(),
            kidNames = '';
        console.log(savedRec.get('name')); // SAVED Homer Simpson = CORRECT!
        Ext.each(savedRec.getKids().getRange(), function(kid){
            kidNames += kid.get('name') + ", ";
        });
        console.log(kids); 
        //Outputs: Bart Simpson, Lisa Simpson, Maggie Simpson = WRONG!!
    }
})

我注意到,如果我检查服务器返回的记录,生成的关联存储(即getKidsStore)包含的记录是原始记录,即它们的名称中没有“SAVED”。然而,返回记录的kids 属性确实包含正确的数据。

如果我正确理解了这个问题,那是Ext.data.reader.Reader 没有使用.save() 响应中包含的关联数据正确更新关联存储。如果是这样,在我看来,这是非常不直观的,因为我希望与处理 store.load() 请求并首先填充生成的关联存储的读取器具有相同的行为。

谁能指出我实现我所追求的行为的正确方向?

免责声明: 此处提出了同样的问题:ExtJs 4 - Load nested data on record save,但没有任何回应。我觉得我的问题更彻底..

编辑:我已经在 Sencha 论坛上发布了这个问题:http://www.sencha.com/forum/showthread.php?270336-Associated-Data-in-Model.save()-Response

编辑(2013 年 8 月 23 日):我用一个完整的例子重写了这篇文章,以及其他发现......

【问题讨论】:

    标签: extjs nested associations extjs4.1


    【解决方案1】:

    我发现了问题,或者更确切地说,困惑在于Ext.data.OperationgetRecords() 方法。此方法返回“将返回操作的初始配置记录,尽管代理可能会在操作初始化后的某个时候修改这些记录的数据。” 根据文档。

    这让 IMO 相当困惑,因为返回的记录确实已更新,但生成的关联存储以及关联的数据并未更新!这就是导致我困惑的原因,看起来好像记录包含来自应用程序服务器的更新数据,但事实并非如此。

    为了帮助我简单的头脑从响应中获取FULLY更新的数据,我在Ext.data.Operation类中添加了一个方法...我只是写了这个方法并没有对其进行了测试,而不是确保我正在寻找的功能,所以使用风险自负!

    请记住,我不调用 store.sync(),而是实例化一个模型并调用 model.save() 方法,因此我的 resultSet 通常只包含一条记录...

    Ext.override(Ext.data.Operation,{
        getSavedRecord: function(){
            var me = this, // operation
                resultSet = me.getResultSet();
    
            if(resultSet.records){
                return resultSet.records[0];
            }else{
                throw "[Ext.data.Operation] EXCEPTION: resultSet contains no records!";
            }
    
        }
    });
    

    现在我能够实现我所追求的功能......

    // Get the unsaved data
    store = Ext.create('App.store.test.Simpson');
    homer = store.getById(1);
    unsavedChildren = '';
    
    Ext.each(homer.getKids().getRange(), function(kid){
        unsavedChildren += kid.get('name') + ",";
    });
    
    console.log(unsavedChildren); // Bart Simpson, Lisa Simpson, Maggie Simpson
    
    // Invokes the UPDATE Method on the proxy
    // See original post for server response
    home.save({
        success: function(rec, op){
            var savedRecord = op.getSavedRecord(), // the magic! /sarcasm
                savedKids   = '';
    
            Ext.each(savedRecord.getKids().getRange(), function(kid){
                savedKids += kid.get('name') + ',';
            });
    
            console.log("Saved Children", savedKids);
    
            /** Output is now Correct!!
                SAVED Bart Simpson, SAVED Lisa Simpson, SAVED Maggie Simpson
              */
        }
    });
    

    编辑 2013 年 12 月 10 日 我还向Ext.data.Model 添加了一个方法,我称之为updateTo,它处理将记录更新到提供的记录,它还处理关联。我将它与上述getSavedRecord 方法结合使用。请注意,这不处理任何 belongsTo 关联,因为我没有在我的应用程序中使用它们,但该功能很容易添加。

    /**
     * Provides a means to update to the provided model, including any associated data
     * @param {Ext.data.Model} model The model instance to update to. Must have the same modelName as the current model
     * @return {Ext.data.Model} The updated model
     */
    updateTo: function(model){
        var me = this,
        that = model,
        associations = me.associations.getRange();
    
        if(me.modelName !== that.modelName)
        throw TypeError("updateTo requires a model of the same type as the current instance ("+ me.modelName +"). " + that.modelName + " provided.");
    
        // First just update the model fields and values
        me.set(that.getData());
    
        // Now update associations
        Ext.each(associations, function(assoc){
        switch(assoc.type){
            /**
             * hasOne associations exist on the current model (me) as an instance of the associated model.
             * This instance, and therefore the association, can be updated by retrieving the instance and
             * invoking the "set" method, feeding it the updated data from the provided model.
             */
            case "hasOne":
                var instanceName  = assoc.instanceName,
                    currentInstance = me[instanceName],
                    updatedInstance = that[instanceName];
    
                 // Update the current model's hasOne instance with data from the provided model
                 currentInstance.set(updatedInstance.getData());
    
                break;
    
            /** 
             * hasMany associations operate from a store, so we need to retrieve the updated association
             * data from the provided model (that) and feed it into the current model's (me) assocStore
             */
            case "hasMany":
                var assocStore = me[assoc.storeName],
                    getter     = assoc.name,
                    newData    = that[getter]().getRange();
    
                // Update the current model's hasMany association store with data from the provided model's hasMany store
                assocStore.loadData(newData);
                break;
    
            // If for some reason a bogus association type comes through, throw a type error
            // At this time I have no belongsTo associations in my application, so this TypeError
            // may one day appear if I decide to implement them.
            default:
                throw TypeError("updateTo does not know how to handle association type: " + assoc.type);
                break;
        }
        });
    
        // Commit these changes
        me.commit();
    
        return me;
    }
    

    所以基本上我会做这样的事情(理论上这将在 Order 控制器中)

    doSaveOrder: function(order){
        var me = this,                       // order controller 
            orderStore = me.getOrderStore(); // magic method
    
        // Save request
        order.save({
            scope: me,
            success: function(responseRecord, operation){ 
                // note: responseRecord does not have updated associations, as per post
                var serverRecord = operation.getSavedRecord(),
                    storeRecord  = orderStore.getById(order.getId());
    
                switch(operation.action){
                    case 'create':
                        // Add the new record to the client store
                        orderStore.add(serverRecord);
                    break;
    
                    case 'update':
                        // Update existing record, AND associations, included in server response
                        storeRecord.updateTo(serverRecord);
                    break;
                }
            }
        });
    }
    

    我希望这对像我一样困惑的人有所帮助!

    【讨论】:

      【解决方案2】:

      完全同意你的看法。真是奇怪的行为。它应该更新记录上的关联存储。这就是我解决这个问题的方法(基本上只是通过阅读器运行响应!):

       success: function(record, operation) {
           var newRecord= me.getMyModel().getProxy().reader.read(operation.response).records[0];
       }
      

      【讨论】:

      • 很好的解决方案!出于某种原因,我发现从模型手动调用代理的读取操作时,存储并不总是更新......所以我在模型类上添加了一个方法“updateTo”,它处理将实例更新为提供的记录(从服务器的响应中检索),包括关联存储。相同的想法,只是方法不同:)
      • 在我的答案中添加了updateTo 方法,以便更清晰/更有帮助。
      【解决方案3】:

      在 ExtJS 6.2 中问题仍然存在(或再次存在)。 我的解决方案:

      /**
       * In Ext.data.reader.Reader::extractRecord the call readAssociated reads out the hasMany associations and processes them.
       * This works perfectly for Model.load() since internally a Model is used as record variable in extractRecord. 
       * For Model.save() record extractRecord contains just the Object with the received data from the PUT request, 
       *  therefore readAssociated is never called and no associations are initialized or updated.
       * The following override calls readAssociated if necessary in the save callback.
       */
      Ext.override(Ext.data.Model, {
          save: function(options) {
              options = Ext.apply({}, options);
              var me = this,
                  includes = me.schema.hasAssociations(me),
                  scope  = options.scope || me,
                  callback,
                  readAssoc = function(record) {
                      //basicly this is the same code as in readAssociated to loop through the associations
                      var roles = record.associations,
                          key, role;
                      for (key in roles) {
                          if (roles.hasOwnProperty(key)) {
                              role = roles[key];
                              // The class for the other role may not have loaded yet
                              if (role.cls) {
                                  //update the assoc store too                            
                                  record[role.getterName]().loadRawData(role.reader.getRoot(record.data));
                                  delete record.data[role.role];
                              }
                          }
                      }
      
                  };
      
              //if we have includes, then we can read the associations
              if(includes) {
                  //if there is already an success handler, we have to call both
                  if(options.success) {
                      callback = options.success;
                      options.success = function(rec, operation) {
                          readAssoc(rec);
                          Ext.callback(callback, scope, [rec, operation]);
                      };
                  }
                  else {
                      options.success = readAssoc;
                  }
              }
              this.callParent([options]);
          }
      });
      

      【讨论】:

      • 很高兴知道它在以后的版本中仍然以这种方式运行,很困惑为什么 Sencha 团队不认为这是一种奇怪的行为......
      【解决方案4】:

      如果您的 ID 字段有值,那么 ExtJS 将始终调用更新。如果您没有向 id 字段写入任何值或将其设置为 null,则应调用 create。我猜您正在尝试使用现有记录调用保存,因此它将始终调用更新。这是一种期望的行为。

      【讨论】:

      • 这个问题与创建/更新请求无关......仅与返回相关数据和 ExtJS 相应地更新商店记录有关,这根本不起作用......如图所示。记录必须从服务器完全重新加载(强制进行无用的往返)或使用服务器响应中提供的数据手动更新,我的回答中详细描述了该过程。
      猜你喜欢
      • 1970-01-01
      • 2019-11-30
      • 2012-07-08
      • 1970-01-01
      • 1970-01-01
      • 2023-03-15
      • 1970-01-01
      • 2016-04-03
      • 1970-01-01
      相关资源
      最近更新 更多