猫鼬在 for 循环中使用 set 如果 extist 或 put if not



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

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

假设我有一个一年的数组,我想用另一个使用 for 循环迭代创建的数组更新目标,我想做的是有一个 x = 0 到 5 的 for 循环,其中 x 是匹配数字,得分 [x] 代表该比赛(去年(的进球数。我想在播放器文档中查看匹配号码是否存在,如果存在,我会使用 findOneAndUpdate 和 $set 来设置分数[x],但它不存在,我想使用 $push 用分数[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 时(匹配找到 == null(,而不是执行 findOneAndUpdate 与$push来创建它,它将继续迭代,因此将尝试对 k = 1 的值做同样的事情(第二年澄清(,并且需要再次移动以查找 OneAndUpdate 与$push因为它不会被找到等等创建分数 = (x * 10( + k 的值,之后,它将返回并使用 $push 和分数 = x * 10 进行查找OneAndUpdate。我知道这是它的正常工作,因为单线程,但我真的需要有所需的输出。 感谢大家,很抱歉长时间阅读。

问题是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。

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

相关内容

最新更新