简体   繁体   English

ZCCADCDEDB567ABAE643E15DCF0974E503Z findOneAndUpdate 在 for 循环中使用 set if extist 或 put if doesn't

[英]Mongoose findOneAndUpdate in a for loop using set if extist or put if doesn't

I am having a problem with concurrency trying to use findOneAndUpdate with the mongoose in a for a loop.我在尝试在 for 循环中将 findOneAndUpdate 与 mongoose 一起使用时遇到并发问题。 Don' t focus on the example given but only on its method.不要关注给出的例子,而只关注它的方法。 Let' s say i have this schema:假设我有这个架构:

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

Assuming that I have a year's array and I would like to update goals with another array created with a for loop iteration, what I am looking to do is to have a for loop for x = 0 to 5 where x is the matchNumber and scores[x] represents the number of goals for that match (for the last year).假设我有一年的数组,我想用另一个用 for 循环迭代创建的数组来更新目标,我想做的是为 x = 0 到 5 设置一个 for 循环,其中 x 是 matchNumber 和 score[ x] 代表那场比赛的进球数(去年)。 I would like to look in the Player document if the matchNumber exists and if it exists I would use findOneAndUpdate with $set to set scores[x] but it doesn' t exist I would like to create it with scores[x] using $push.如果 matchNumber 存在,我想查看 Player 文档,如果存在,我将使用 findOneAndUpdate 和 $set 来设置 score[x],但它不存在我想使用 $push 使用 score[x] 创建它. Let' s give code for the easiest understanding ("Maradona" will be our player name):让我们给出最容易理解的代码(“马拉多纳”将是我们的球员名字):

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

What I would have at the end is:我最后会得到的是:

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

but what i get is something like:但我得到的是这样的:

{
    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
        }
}

or a mix of them.或它们的混合。 This happens because of the code will attempt to make the findOneAndUpdate with $set and when the matchNumber is not found (matchfound == null) instead of executing the findOneAndUpdate with $push to create it, it will keep on iterating so will try to do the same with the value of k = 1 (the second year to clarify) and will need to move again to findOneAndUpdate with $push because of it will not be found and so the value with scores = (x * 10) + k is created and after that, it will come back and make the findOneAndUpdate with $push and scores = x * 10. I understand that this is its normal working because of mono threading but I really need to have the desired output.发生这种情况是因为代码将尝试使用 $set 生成 findOneAndUpdate 并且当找不到 matchNumber (matchfound == null) 而不是使用 $push 执行 findOneAndUpdate 来创建它时,它将继续迭代,因此将尝试这样做与 k = 1 的值相同(第二年要澄清),并且需要再次移动到 findOneAndUpdate 与 $push 因为它不会被发现,所以分数 = (x * 10) + k 的值被创建之后,它将返回并使用 $push 和分数 = x * 10 进行 findOneAndUpdate。我知道这是它的正常工作,因为 mono 线程但我真的需要有所需的 output。 Thanks to all and sorry for the long reading.感谢所有人,很抱歉长时间阅读。

The problem there is that findOneAndUpdate is asynchronous.问题在于findOneAndUpdate是异步的。 Each iteration through the for loop calls Player.findOneAndUpdate and passes it a callback function.通过 for 循环的每次迭代都会调用Player.findOneAndUpdate并向其传递一个回调 function。 The findOneAndUpdate call returns immediately, and the loop moves on to the next iteration. findOneAndUpdate 调用立即返回,循环继续进行下一次迭代。

At some point in the future, the query to the mongo completes and the callback is called with the return value, since there are multiple asynchronous calls staged, the order they complete is not strictly defined.在未来的某个时刻,对 mongo 的查询完成并使用返回值调用回调,因为有多个异步调用暂存,它们完成的顺序没有严格定义。

You might try using await with each call to findOneAndUpdate to make them synchronous, which will serialize the requests.您可以尝试在每次调用 findOneAndUpdate 时使用await以使它们同步,这将序列化请求。

If you are using MongoDB 4.2, you can use the pipeline form of update to be able to do both insert and update array elements in an single statement:如果您使用的是 MongoDB 4.2,则可以使用更新的管道形式,以便能够在单个语句中插入和更新数组元素:

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

In order to make sure these are applied in the correct order, you could build and array of all of the operations to apply, like:为了确保这些以正确的顺序应用,您可以构建和排列所有要应用的操作,例如:

[
  {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 }]
                 ]
             }
          }}]
 }}

and then pass that array to bulkWrite with the ordered=true option.然后使用ordered=true选项将该数组传递给bulkWrite

Also note that if you are able to build an array of all of the scores that need to be updated, you could use a similar statement to update all of the scores for a single player in a one operation, which should perform a bit better.另请注意,如果您能够构建一个包含所有需要更新的分数的数组,您可以使用类似的语句在一次操作中更新单个玩家的所有分数,这应该会更好一些。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM