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

时间:2014-02-07 22:32:02

标签: node.js mongodb mongoose

这是我的代码:

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

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

2 个答案:

答案 0 :(得分:18)

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

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

    当然还有一个回调,但是如果找到副本,它会再次写入数据。这是一个问题是否真的取决于用例。

    我已经对你的代码进行了一些清理......但它确实很简单,回调应该很清楚。函数的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);
        });
    }
    

    或者,您可以使用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
    });
    

答案 1 :(得分:10)

我非常喜欢WiredPrairie's answer,但他的承诺实施过于复杂。

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

猫鼬3.8.x

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

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

models.Value.findOne({title:title}).exec()也会返回一个承诺,因此不需要回调或任何额外的投射。

如果你通常不在你的代码中使用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);
}

以前版本的Mongoose

如果您使用3.8.0之前的任何Mongoose版本,那么您可能需要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;
        }
    });
}

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

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