【问题标题】:Mongoose - Increment Field On Subdoc if exists, else create newMongoose - 如果存在,则在 Subdoc 上增加字段,否则创建新的
【发布时间】:2016-09-27 20:17:27
【问题描述】:

我想要做什么。

我有一个userSchema,其中包含operationCountSchemaobjects 的列表。我要做的是创建一个静态方法,如果存在(由month_id 标识)字段,则更新这些操作计数子文档之一上的count 字段。如果当前月份不存在operationCountSchema 文档,则应创建一个新文档。有没有办法在猫鼬中实现这种行为?我曾尝试使用 upsert 无济于事。如何做到这一点?谢谢。

代码

var operationCountSchema = mongoose.Schema({
    month_id: String,
    count: { type: Number, default: 0 }
}, {_id : false});

var userSchema = mongoose.Schema({
    username : { type: String, unique: true, required: true },
    email: { type: String, unique: true, required: true },
    password: String,
    operation_counts: [operationCountSchema]
});

userSchema.statics.incrementOperationCount = function(userID, callback) {
    var currDate = new Date();
    var dateIdentifier = currDate.getFullYear() + "-" + currDate.getMonth();
    //NEED TO INCREMENT OPERATION COUNT IF ONE FOR MONTH EXISTS, 
    //ELSE IF IT DOES NOT EXIST, CREATE A NEW ONE.
}

此外,欢迎任何有关实现此功能的替代方法的建议。

【问题讨论】:

  • 通过静态方法传递值并检查子文档是否存在(使用this关键字)。如果存在修改并调用回调。否则使用this.sub-docment = <your-values> 创建子文档并调用回调。一旦回调被调用,使用猫鼬文档save() 方法来保存修改后的文档。
  • month_id 需要匹配什么来增加计数?
  • @Chinni month_id 需要匹配 dateIdentifier。

标签: javascript node.js mongodb mongoose


【解决方案1】:

因此您可以拥有mongoose.find()mongoose.findOne() 并检查子文档是否存在。如果不是,我们可以创建一个新对象,如果是,我们可以递增并保存。

我在这里使用mongoose.findOne()。请参阅文档here

userSchema.statics.incrementOperationCount = function(userID, callback) {
var currDate = new Date();
var dateIdentifier = currDate.getFullYear() + "-" + currDate.getMonth();
    //NEED TO INCREMENT OPERATION COUNT IF ONE FOR MONTH EXISTS, 
    //ELSE IF IT DOES NOT EXIST, CREATE A NEW ONE.
    operationCountSchema.findOne({'month_id': dateIdentifier}, function(err, subDoc) {
        // If there is an error in finding the document, catch them
        if(err) {
            // Handle errors
            return err;
        }
        // If you find a document, increment the `count` and save
        if(subDoc) {
            subDoc.count += 1;
            subDoc.save(function(err2) {
                if(err2) {
                    // Handle errors
                    return err2;
                } else {
                    return "Success";
                }
            });
        } 
        // If no document is found, create a new one
        else {
            // Populate the values to create the object
            var data = {
                "month_id": dateIdentifier,
                "count": 0
            };
            operationCountSchema.create(data, function(err3, subDoc) {
                if(err3) {
                    // Handle errors
                    return err3;
                }
                // Else return success
                return "Success";
            });
        }
    });
};

让我知道我是否理解了您的问题或没有解决问题。

【讨论】:

  • 您没有解决并发问题或竞争条件。在findOnesavecreate 之间,另一个可能的数据库编写者可能已经更新/创建了计数。
  • 这么多人有权限同时更新同一个文档记录?
  • 在 MongoDB 中,文档完全有可能在 findOnesave/create 之间 期间被另一个作者更新,因为 MongoDB 没有事务。跨度>
  • 是的。同意。我刚刚在官方文档docs.mongodb.com/ecosystem/use-cases/… 中遇到了这个问题。我之前没有实现或使用过它。请检查一下,让我也知道结论是什么。
  • 我对这种方法很熟悉,但我想知道它是否适用于这种特殊情况(特别是因为您还必须处理一个记录甚至可能还不存在的事实)。我在回答中建议的 findOneAndUpdate 操作使用 atomic 操作 (findAndModify()) 来处理并发问题。
【解决方案2】:

我想你想要findOneAndUpdate()upsert : true

operationCountSchema.findOneAndUpdate({
  month_id : dateIdentifier,
}, { 
  $inc : { count : 1 }
}, {
  upsert : true
}, callback);

(未经测试)

【讨论】:

  • 我认为您误解了我的代码/问题。 month_id 不是唯一的。操作计数模式需要根据子文档的用户进行查询。
  • @AnthonyDito 啊,我以为您使用的是单独的集合,但您使用的是子文档。在这种情况下,我认为您无法一步完成,这意味着任何更新都容易出现竞争条件。
【解决方案3】:

您可以分两步完成,这是mongo shell中的示例:

mongos> db.collection.findOne()    
{
    "username" : "mark",
    "email" : "admin@example.com",
    "password" : "balalalala",
    "operation_counts" : [
        {
            "month_id" : "2016-05",
            "count" : 6
        }
    ]
}

首先,确保子文档存在,如果不只是创建一个使用$addToSet

mongos> db.collection.update({username:"mark", "operation_counts.month_id": {$ne:"2016-05"}}, {$addToSet: {"operation_counts":{month_id: "2016-05", count:0}}})
WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
// only update when the subdoc of specified month not exists
mongos> db.collection.update({username:"mark", "operation_counts.month_id": {$ne:"2016-06"}}, {$addToSet: {"operation_counts":{month_id: "2016-06", count:0}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

mongos> db.collection.findOne()
{
    "_id" : ObjectId("575636c21e9b27fe715df654"),
    "username" : "mark",
    "email" : "admin@example.com",
    "password" : "balalalala",
    "operation_counts" : [
        {
            "month_id" : "2016-05",
            "count" : 6
        },
        {
            "month_id" : "2016-06",
            "count" : 0
        }
    ]
}

然后,增加 count 字段。

mongos> db.collection.update({username:"mark", "operation_counts.month_id": "2016-06"}, {$inc:{ "operation_counts.$.count":1 }})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

mongos> db.collection.findOne()
{
    "_id" : ObjectId("575636c21e9b27fe715df654"),
    "username" : "mark",
    "email" : "admin@example.com",
    "password" : "balalalala",
    "operation_counts" : [
        {
            "month_id" : "2016-05",
            "count" : 6
        },
        {
            "month_id" : "2016-06",
            "count" : 1
        }
    ]
}

【讨论】:

  • 我最终实现了类似的东西。这似乎是最好的解决方案。就目前而言。我会给你赏金。
  • 呃,我是用$ne操作符来匹配的,所以要保证其他条件可以使用索引,operation_counts数组不会太大。
猜你喜欢
  • 2018-09-13
  • 1970-01-01
  • 2016-08-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-17
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多