mongoose:检测插入的文档是否重复,如果是,则返回现有文档

dop*_*man 13 mongoose mongodb node.js

这是我的代码:

    var thisValue = new models.Value({
        id:id,
        title:title //this is a unique value
    });

    console.log(thisValue);

    thisValue.save(function(err, product, numberAffected) {
        if (err) {
            if (err.code === 11000) { //error for dupes
                console.error('Duplicate blocked!');
                models.Value.find({title:title}, function(err, docs)
                   {
                       callback(docs) //this is ugly
                   });
            }
            return;
        }

        console.log('Value saved:', product);
        if (callback) {
            callback(product);
        }
    });
Run Code Online (Sandbox Code Playgroud)

如果我检测到正在尝试插入重复项,我会阻止它.但是,当发生这种情况时,我想返回现有文档.正如你所看到的,我已经实现了一串回调,但这是丑陋的,它是不可预测的(即我怎么知道将调用哪个回调?我如何传递正确的回调?).有谁知道如何解决这个问题?任何帮助赞赏.

Wir*_*rie 18

虽然您的代码不处理一些错误情况,并且使用了错误的find函数,但一般流程通常会给您想要的工作.

  1. 如果存在重复之外的错误,则不会调用回调,这可能会导致NodeJs应用程序中出现下游问题
  2. 因为密钥是唯一的,所以使用findOne而不是find只有一个结果.否则,它将返回一个数组.
  3. 如果你的回调期望传统error作为第一个参数,你可以直接将回调传递给findOne函数而不是引入匿名函数.
  4. 您最后也可能需要查看findOneAndUpdate,具体取决于您的最终架构和逻辑.

如上所述,您可以使用findOneAndUpdate,但需要额外费用.

function save(id, title, callback) {
    Value.findOneAndUpdate(
       {id: id, title: title}, /* query */
       {id: id, title: title}, /* update */
       { upsert: true}, /* create if it doesn't exist */
       callback);
}
Run Code Online (Sandbox Code Playgroud)

当然还有一个回调,但如果发现重复,它会再次写入数据.这是一个问题是否真的取决于用例.

我已经对你的代码进行了一些清理......但它确实很简单,回调应该很清楚.在callback该函数总是收到任何新保存的文档或被匹配为重复的一个.调用函数调用saveNewValue错误并正确处理它是责任.您将看到我如何确定无论错误类型如何都会调用回调,并始终以一致的方式调用结果.

function saveNewValue(id, title, callback) {
    if (!callback) { throw new Error("callback required"); }
    var thisValue = new models.Value({
        id:id,
        title:title //this is a unique value
    });

    thisValue.save(function(err, product) {
        if (err) {
            if (err.code === 11000) { //error for dupes
                return models.Value.findOne({title:title}, callback);
            }            
        }    
        callback(err, product);
    });
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用promise模式.这个例子是使用when.js.

var when = require('when');

function saveNewValue(id, title) {
    var deferred = when.defer();

    var thisValue = new models.Value({
        id:id,
        title:title //this is a unique value
    });

    thisValue.save(function(err, product) {
        if (err) {
            if (err.code === 11000) { //error for dupes
                return models.Value.findOne({title:title}, function(err, val) {
                    if (err) {
                        return deferred.reject(err);
                    }
                    return deferred.resolve(val);
                });
            }
            return deferred.reject(err);
        }
        return deferred.resolve(product);
    });

    return deferred.promise;
}

saveNewValue('123', 'my title').then(function(doc) {
    // success
}, function(err) {
    // failure
});
Run Code Online (Sandbox Code Playgroud)


Leo*_*tny 10

我非常喜欢WiredPrairie的答案,但他的承诺实施过于复杂.

所以,我决定添加自己的promise实现.

猫鼬 3.8.x

如果您使用的是最新的Mongoose,3.8.x那么就不需要使用任何其他promise模块,因为3.8.0model .create()方法会返回一个promise:

function saveNewValue(id, title) {
    return models.Value.create({
        id:id,
        title:title //this is a unique value
    }).then(null, function(err) {
        if (err.code === 11000) {
            return models.Value.findOne({title:title}).exec()
        } else {
            throw err;
        }
    });
}

saveNewValue('123', 'my title').then(function(doc) {
    // success
    console.log('success', doc);
}, function(err) {
    // failure
    console.log('failure', err);
});
Run Code Online (Sandbox Code Playgroud)

models.Value.findOne({title:title}).exec() 也返回一个promise,因此不需要回调或任何额外的转换.

如果你通常不在你的代码中使用promises,这里是它的回调版本:

function saveNewValue(id, title, callback) {
    models.Value.create({
        id:id,
        title:title //this is a unique value
    }).then(null, function(err) {
        if (err.code === 11000) {
            return models.Value.findOne({title:title}).exec()
        } else {
            throw err;
        }
    }).onResolve(callback);
}
Run Code Online (Sandbox Code Playgroud)

以前版本的Mongoose

如果您之前使用的是任何Mongoose版本3.8.0,那么您可能需要when模块的一些帮助:

var when = require('when'),
    nodefn = require('when/node/function');

function saveNewValue(id, title) {
    var thisValue = new models.Value({
        id:id,
        title:title //this is a unique value
    });

    var promise = nodefn.call(thisValue.save.bind(thisValue));

    return promise.spread(function(product, numAffected) {
        return product;
    }).otherwise(function(err) {
        if (err.code === 11000) {
            return models.Value.findOne({title:title}).exec()
        } else {
            throw err;
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

我正在使用nodefn.call辅助函数将回调式.save()方法转换为promise.Mongoose团队承诺在其中添加承诺支持Mongoose 4.x.

然后我使用.spreadhelper方法从.save()回调中提取第一个参数.