【问题标题】:Sails.js model - insert or update recordsSails.js 模型 - 插入或更新记录
【发布时间】:2014-11-14 05:24:09
【问题描述】:

一直在试用 Sails.js,我正在编写一个从第三方 API 导入数据并保存到 MySQL 表中的应用程序。基本上,我正在尝试将数据同步到我的应用程序以进行进一步分析、更新我的记录或根据需要创建新记录。

我查看了 Sails 的 API,看到了查找、创建和更新记录的方法,但没有根据情况插入/更新记录的内置方法。我是否忽略了某些内容,还是需要我自己实现?

如果我必须自己实现,有谁知道插入/更新的良好设计模式吗?

这就是我认为的样子……

_.each(importedRecords, function(record){
  MyModel.find({id: record.id}).exec(function findCB(err, found){
    if(found.length){
      MyModel.update(record.id, task).exec(function(err, updated){
        if(err) { //returns if an error has occured, ie id doesn't exist.
          console.log(err);
        } else {
          console.log('Updated MyModel record '+updated[0].name);
        }
      });
    }else{
       MyModel.create(record).exec(function(err, created){
         if(err) { //returns if an error has occured, ie invoice_id doesn't exist.
           console.log(err);
         } else {
           console.log('Created client record '+created.name);
         }
       });
     }
   });
 });

我是朝着正确的方向前进,还是有更优雅的解决方案?

另外,我在这个应用程序中处理了很多不同的模型,这意味着在我的每个模型中重新创建这段代码。有没有办法可以扩展基础模型对象为所有模型添加此功能。

谢谢, 约翰

【问题讨论】:

    标签: javascript sails.js


    【解决方案1】:

    您的解决方案是正确的。使用水线(sails.js 的 ORM)没有其他方法可以做到这一点。 但是有几个数据库有针对这种情况的功能:

    MySQL

    REPLACE INTO table SET id = 42, foo = 'bar'; (使用主键或唯一键。如果您使用 auto_increment 则非常糟糕 ;-)

    在 Waterline 中,您可以使用 Model.query()-Function 直接执行 SQL(参见:http://sailsjs.org/#/documentation/reference/waterline/models/query.html

    MongoDB

    db.collection.update(
      <query>,
      <update>,
      { upsert: true }
    )
    

    upsert 标志的意思是:如果您因为在查询中没有找到任何内容而无法更新它,请创建此元素!

    在 Waterline 中,您可以使用 Model.native()-Function 直接执行 mongoDB-Commands(参见:http://sailsjs.org/#/documentation/reference/waterline/models/native.html

    结论

    您需要快速执行(当然,如果您有很多请求)我建议使用本机/sql 函数。但总的来说,我真的很喜欢 ORM 系统的灵活性,而且每次使用特定于数据库的函数时,都很难处理。

    【讨论】:

      【解决方案2】:

      感谢 user3351722,我也更喜欢使用 ORM 系统。我只是尝试将上述解决方案实现为通用模型方法。 (基于Inherit attributes and lifecycle functions of Sails.js models)。

      我编辑了 config/models.js 并添加了一个新函数 insertOrUpdate,它接受索引列的名称、我要插入或更新的数据以及一个回调函数。

      module.exports.models = {
        insertOrUpdate: function(key, record, CB){
          var self = this; // reference for use by callbacks
          var where = {};
          where[key] = record[key]; // keys differ by model
          this.find(where).exec(function findCB(err, found){
            if(err){
              CB(err, false);
            }
            // did we find an existing record?
            if(found && found.length){
              self.update(record[key], record).exec(function(err, updated){
                if(err) { //returns if an error has occured, ie id doesn't exist.
                  CB(err, false);
                } else {
                  CB(false, found[0]);
                }
              });
            }else{
              self.create(record).exec(function(err, created){
                if(err) { //returns if an error has occured, ie invoice_id doesn't exist.
                  CB(err, false);
                } else {
                  CB(false, created);
                }
              });
            }
          });
        }
      };
      

      这仅适用于具有索引的表/集合。我不知道如何从水线模型中反省键名,所以我将字段名作为字符串传入。

      以下是在控制器中使用该方法的方法……

      _.each(clients, function(client){
        Client.insertOrUpdate('client_id', client, function(err, updated){
          if(err) { //returns if an error has occured, ie invoice_id doesn't exist.
            sails.log(err);
          } else {
            sails.log('insertOrUpdate client record ', updated.organization); //+updated[0].name
          }
        });
      });
      

      我已经用三种不同的模型尝试过这种方法,到目前为止,效果很好。它们都是 MySQL 表,模型都有定义的索引。如果您使用不同的数据存储,您的里程数可能会很大。

      如果有人看到改进的方法,请告诉我们。

      【讨论】:

        【解决方案3】:

        Sails 0.10 有 findOrCreate(criteria, attributes, callback),请参阅 Sails Docs

        criteria 是“查找”位的搜索条件(与 find() 语法相同)。

        attributes 是“create”位未找到时使用的数据(与 create() 语法相同)。

        这是一个例子:

        MyModel.findOrCreate({name:'Walter'},{name:'Jessie'}, function (err, record){ console.log('What\'s cookin\' '+record.name+'?');

        另请注意,Waterline repository(参见tests 示例)和Waterline documentation 中记录了其他复合查询方法:

        默认情况下,以下每个基本方法都可用于 集合实例:

        • 找到一个
        • 找到
        • 创建
        • 更新
        • 销毁
        • 计数

        此外,您还有以下辅助方法:

        • 创建每个
        • findOrCreateEach *
        • findOrCreate
        • findOneLike
        • 找到喜欢
        • 开始于
        • 结束
        • 包含

        根据您的 Collection 属性,您还可以使用动态查找器。所以 给定名称属性,以下查询将可用:

        • findOneByName
        • findOneByNameIn
        • findOneByNameLike
        • findByName
        • findByNameIn
        • findByNameLike
        • countByName
        • countByNameIn
        • countByNameLike
        • nameStartsWith
        • nameEndsWith
        • 名称包含

        至于忽略某些东西,它就在那里,但它不在主要的 Sails 文档中,所以答案是肯定的和否定的,所以不要担心 :)

        【讨论】:

        • 感谢您的回复。我在搜索的早期看到了该功能,但由于我无法知道它是否找到或必须创建记录,所以我决定实现我想要的功能。如果找到的操作返回 true,那么我想用新数据更新记录。
        • 不客气。这听起来像是有用的功能。也许你应该提交一张 Waterline 的增强票。
        • FindOrCreate() 的 Sails Docs 链接已损坏,缺少关键信息。 sailsjs.org/#!/documentation/reference/waterline/models/…
        • 对,似乎同时拥有 upsertfindOrCreate 方法会很有用,它们提供 存在标志next() 方法用于继续分别到insertcreate
        • @KennethBenjamin,关于您引用的 Sails 文档,您似乎可以为 criteriarecord 传入一组对象。这是否会检查每个条件对象,如果不存在,是否映射到记录对象的索引
        【解决方案4】:

        我已经重写了 Critical Mash 代码,因此它的代码更少,更通用。 现在您可以像调用 findOrCreate 一样调用 updateOrCreate。它看起来像这样:

        module.exports.models = {
            updateOrCreate: function(criteria, values){
                var self = this; // reference for use by callbacks
                // If no values were specified, use criteria
                if (!values) values = criteria.where ? criteria.where : criteria;
        
                return this.findOne(criteria).then(function (result){
                  if(result){
                    return self.update(criteria, values);
                  }else{
                    return self.create(values);
                  }
                });
            }
        };
        

        这样你就可以用同样的方式编写标准。无需在密钥上工作,代码也简单得多。

        【讨论】:

        • 美丽的答案。经您的许可(和信用),我想基于此创建一个对 Sails 的拉取请求。
        • @AaronWagner 是的。请做一个拉请求。
        • 与问题和“帆中缺少的东西”完全相关。谢谢! @AaronWagner 有任何拉取请求更新吗?
        • 我将此添加到我的 config/models.js 文件中,位于module.exports.models = { 下,但在那里不起作用。当我从控制器调用它时出现方法未定义错误。所以我将它作为一种方法添加到我试图使用它的模型上,它在那里工作。所以+1,很好的答案。不知道为什么它不像你建议的那样在模型文件中工作。
        • 嘿。 @Loubot 我已经更新了代码以支持承诺及其现在的工作。
        【解决方案5】:

        我就是这样做的:我扩展了 config/models.js 以包含该功能,并检查适配器是否具有正确的方法。您可以将其称为承诺或通常。

            var normalize = require('sails/node_modules/waterline/lib/waterline/utils/normalize');
            var hasOwnProperty = require('sails/node_modules/waterline/lib/waterline/utils/helpers').object.hasOwnProperty;
            var defer = require('sails/node_modules/waterline/lib/waterline/utils/defer');
            var noop = function() {};
        
        module.exports.models = {
        
            /**
             * [updateOrCreate description]
             * @param  {[type]}   criteria [description]
             * @param  {[type]}   values   [description]
             * @param  {Function} cb       [description]
             * @return {[type]}            [description]
            */
        
            updateOrCreate: function (criteria, values, cb) {
                var self = this; 
                var deferred;
        
                // Normalize Arguments
                if(typeof cb !== 'function') {
                   deferred = defer();
                }
                cb = cb || noop;
        
                criteria = normalize.criteria(criteria);
        
                if (criteria === false) {
                    if(deferred) {
                        deferred.resolve(null);
                    }
                    return cb(null, []);
                }
                else if(!criteria) {
                    if(deferred) {
                        deferred.reject(new Error('No criteria or id specified!'));
                    }
                    return cb(new Error('No criteria or id specified!'));
                }
        
                // Build Default Error Message
                var errFind = 'No find() method defined in adapter!';
                var errUpdate = 'No update() method defined in adapter!';
                var errCreate = 'No create() method defined in adapter!';
        
                // Find the connection to run this on
                if(!hasOwnProperty(self.adapter.dictionary, 'find')){
                    if(deferred) {
                        deferred.reject(errFind);
                    }
                    return cb(new Error(errFind));
                }
                if(!hasOwnProperty(self.adapter.dictionary, 'update')){ 
                    if(deferred) {
                        deferred.reject(errUpdate);
                    }
                    return cb(new Error(errUpdate));
                }
                if(!hasOwnProperty(self.adapter.dictionary, 'create')) {
                    if(deferred) {
                        deferred.reject(errCreate);
                    }
                    return cb(new Error(errCreate));
                }
        
                var connNameFind = self.adapter.dictionary.find;
                var adapterFind = self.adapter.connections[connNameFind]._adapter;
        
                var connNameUpdate = self.adapter.dictionary.update;
                var adapterUpdate = self.adapter.connections[connNameUpdate]._adapter;
        
                var connNameCreate = self.adapter.dictionary.create;
                var adapterCreate = self.adapter.connections[connNameCreate]._adapter;
        
                adapterFind.find(connNameFind, self.adapter.collection, criteria, normalize.callback(function before (err, results){
        
                    if (err) {
                        if(deferred) {
                            deferred.reject(err);
                        }
                        return cb(err);
                    }
        
                    if(results && results.length > 0){
                        adapterUpdate.update(connNameUpdate, self.adapter.collection, criteria, values, normalize.callback(function afterwards (err, updatedRecords) {
                            if (err) {
                                if(deferred) {
                                    deferred.reject(err);
                                }
                                return cb(err);
                            }
                            deferred.resolve(updatedRecords[0]);
                            return cb(null, updatedRecords[0]);
                        }));
                    }else{
                        adapterCreate.create(connNameCreate, self.adapter.collection, values, normalize.callback(function afterwards (err, createdRecord) {
                            if (err) {
                                if(deferred) {
                                    deferred.reject(err);
                                }
                                return cb(err);
                            }
                            deferred.resolve(createdRecord);
                            return cb(null, createdRecord);
                        }));
                    }
                }));
        
                if(deferred) {
                    return deferred.promise;
                }
            }
        }
        

        【讨论】:

        • 这确实会创建一个Race 条件,因为可以在查找之后创建记录。除支持事务的数据库外,其他所有数据库都是如此。从技术上讲,最好只调用 create 函数并侦听错误,然后通知您更新。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-13
        • 2020-11-21
        • 2021-10-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多