【问题标题】:Mongoose findOneAndUpdate in a for loop using set if extist or put if doesn'tMongoose findOneAndUpdate 在 for 循环中使用 set if extist 或 put if doesn't
【发布时间】:2020-06-24 09:10:08
【问题描述】:

我在尝试在 for 循环中将 findOneAndUpdate 与猫鼬一起使用时遇到了并发问题。 不要关注给出的例子,而只关注它的方法。 假设我有这个架构:

var PlayerSchema = new Schema({
    playername: String,
    playerstats:[{
        matchNumber: Number,
        goals: {
            type: Number,
            default: 0
        }
    }]
})

假设我有一年的数组,我想用另一个用 for 循环迭代创建的数组来更新目标,我想做的是为 x = 0 到 5 设置一个 for 循环,其中 x 是 matchNumber score[x] 代表那场比赛的进球数(去年)。如果 matchNumber 存在,我想查看 Player 文档,如果存在,我将使用带有 $set 的 findOneAndUpdate 来设置 scores[x],但它不存在我想使用 $push 使用 score[x] 创建它.让我们给出最容易理解的代码(“马拉多纳”将是我们的球员名字):

function iterateYears(){
    years = ["2019", "2020"]
    for (var k = 0; k < years.lenght; k++) {
        updateScores(k)
    }
}
function updateScores(k) {
    var scores = []
    for (var y = 0; y < 6; y++) {
        scores.splice(y, 0, (y * 10) + k)
    }
    //at this point we would have the first time scores = [0, 10, 20, 30, 40] and second time scores = [1, 11, 21, 31, 41]
    for (var x = 0; x < 6; x++) {
        Player.findOneAndUpdate({playername: "Maradona", 'playerstats.matchNumber': x}, {$set: {'playerstats.$.goals': scores[x]}}, function(err, matchfound) {
            if (err) {
                console.log("Err found")
            } else {
                if (matchfound == null) {
                    Player.findOneAndUpdate({playername: "Maradona"}, {$push: {playerstats:{matchNumber: x, goals: scores[x]}}}).exec();
                }
            }
        })
    }
}

最后我会得到的是:

{
    playername: "Maradona",
    playerstats:[
        {
            matchNumber: 0,
            goals: 1
        },
        {    
            matchnumber: 1,
            goals: 11
        },
        {
            matchNumber: 2,
            goals: 21
        },
        {
            matchNumber: 3,
            goals: 31
        },
        {
            matchNumber: 4,
            goals: 41
        }
}

但我得到的是这样的:

{
    playername: "Maradona",
    playerstats:[
        {
            matchNumber: 0,
            goals: 1
        },
        {
            matchNumber: 0,
            goals: 0
        },
        {    
            matchnumber: 1,
            goals: 11
        },
        {    
            matchnumber: 1,
            goals: 10
        },
        {
            matchNumber: 2,
            goals: 21
        },
        {
            matchNumber: 2,
            goals: 20
        },
        {
            matchNumber: 3,
            goals: 31
        },
        {
            matchNumber: 3,
            goals: 30
        },
        {
            matchNumber: 4,
            goals: 41
        },
        {
            matchNumber: 4,
            goals: 40
        }
}

或它们的混合。 发生这种情况是因为代码将尝试使用 $set 来创建 findOneAndUpdate 并且当找不到 matchNumber (matchfound == null) 而不是使用 $push 执行 findOneAndUpdate 来创建它时,它将继续迭代,所以会尝试这样做与 k = 1 的值相同(第二年要澄清),并且需要再次移动到 findOneAndUpdate 与 $push 因为它不会被发现,所以分数 = (x * 10) + k 的值被创建之后,它会返回并使用 $push 和 score = x * 10 生成 findOneAndUpdate。我知道这是它的正常工作,因为单线程但我真的需要有所需的输出。 谢谢大家,很抱歉读了这么久。

【问题讨论】:

    标签: javascript node.js mongodb mongoose


    【解决方案1】:

    问题在于findOneAndUpdate 是异步的。通过 for 循环的每次迭代都会调用 Player.findOneAndUpdate 并将其传递给回调函数。 findOneAndUpdate 调用立即返回,循环继续进行下一次迭代。

    在未来的某个时刻,对 mongo 的查询完成并使用返回值调用回调,因为有多个异步调用暂存,它们完成的顺序没有严格定义。

    您可以尝试在每次调用 findOneAndUpdate 时使用await 以使它们同步,这将序列化请求。

    如果您使用的是 MongoDB 4.2,则可以使用更新的管道形式,以便能够在单个语句中插入和更新数组元素:

    .update(
             {playername: "Maradona"},
             [{$set:{
                 playerstats:{
                     $concatArrays:[
                         {$filter:{
                             input:"$playerstats",
                             cond:{$ne:["$$this.matchNumber",x]}
                         }},
                         [{matchNumber: x, goals: scores[x] }]
                     ]
                 }
              }}]
    )
    

    为了确保以正确的顺序应用这些,您可以构建和排列所有要应用的操作,例如:

    [
      {updateOne:{
        filter: {playername: "Maradona"},
        update: [{$set:{
                 playerstats:{
                     $concatArrays:[
                         {$filter:{
                             input:"$playerstats",
                             cond:{$ne:["$$this.matchNumber",0]}
                         }},
                         [{matchNumber: 0, goals: 1 }]
                     ]
                 }
              }}]
     }},
     {updateOne:{
        filter: {playername: "Maradona"},
        update: [{$set:{
                 playerstats:{
                     $concatArrays:[
                         {$filter:{
                             input:"$playerstats",
                             cond:{$ne:["$$this.matchNumber",1]}
                         }},
                         [{matchNumber: 1, goals: 11 }]
                     ]
                 }
              }}]
     }}
    

    然后使用ordered=true 选项将该数组传递给bulkWrite

    还请注意,如果您能够构建一个包含所有需要更新的分数的数组,您可以使用类似的语句来更新单个玩家的所有分数一个操作,应该会更好一些。

    【讨论】:

    • 非常感谢您的建议。对不起,我完全不明白你的解决方法。您是否建议用数组创建替换 findOneAndUpdate ?当您建议调用 bulkWrite 时?我如何确定只有在数组完成时才会调用它,并且不会将 2019 年的值与 2020 年的值“混合”?
    • 这个想法是在不调用服务器的情况下构建 updateOne 对象数组。然后使用bulkWrite 将阵列一次性发送到服务器,并在选项中指定ordered:true,以便服务器按照它们在阵列中出现的顺序应用更新。如果您需要确保在 2019 年之后处理 2020 年,请按照您希望它们的处理顺序将它们全部构建到同一个数组中。
    • 感谢您清除@Joe。由于 years 数组是动态填充的,我已经解决了将它传递给 updateScores 函数的问题,然后检查它是否是数组中的最后一个元素,如果是,那么我进行更新,否则它将被跳过。所以现在由于 years 数组在所有调用 updateScores 函数的迭代之前完全填充,它只会制作最后一个(我想要的唯一一个)。谢谢大家,这些都是对未来的宝贵建议!
    猜你喜欢
    • 2021-11-09
    • 1970-01-01
    • 2013-09-28
    • 1970-01-01
    • 2014-03-13
    • 1970-01-01
    • 2017-08-02
    • 2019-12-04
    相关资源
    最近更新 更多