Учитывая следующий документ:
{
"_id": ObjectId("57315ba4846dd82425ca2408"),
"myarray": [
{
"key": 1
"mydictionary":
{
"PointA": {
userId: ObjectId("570ca5e48dbe673802c2d035"),
point: 5
},
"PointB":{
userId: ObjectId("613ca5e48dbe673802c2d522"),
point: 2
}
}
},
{
"key": 2
"mydictionary":
{
"PointA": {
userId: ObjectId("570ca5e48dbe673802c2d035"),
point: 5
},
"PointB":{
userId: ObjectId("613ca5e48dbe673802c2d522"),
point: 2
}
}
}
]
}
Мне нужно добавить или обновить новую точку (на основе имени ключа, например, «Точка A» будет обновлена, но должен быть добавлен новый элемент «Point C») в атомарной операции. А также по ключу элемента конкретного массива.
До сих пор я пытался сначала преобразовать словарь в обычный массив и попытаться заставить его работать так. Это обновление. Просто пытаюсь объединить или объединить массив в зависимости от того, существует ли элемент уже или нет.
db.collection.update({
_id: ObjectId("57315ba4846dd82425ca2408")
},
[
{
$set: {
"myarray.mysubarray": {
$cond: [
{
$in: [
ObjectId("570ca5e48dbe673802c2d035"),
"$myarray.mysubarray.userId"
]
},
{
$map: {
input: "$myarray.mysubarray",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{
$eq: [
"$$this.userId",
ObjectId("570ca5e48dbe673802c2d035")
]
},
{
point: 3
},
{}
]
}
]
}
}
},
{
$concatArrays: [
"$myarray.mysubarray",
[
{
userId: ObjectId("570ca5e48dbe673802c2d035"),
point: 4
}
]
]
}
]
}
}
}
])
Может быть, что-то вроде этого:
db.collection.update({},
[
{
"$addFields": {
"myarray": {
"$map": {
"input": "$myarray",
"in": {
"$mergeObjects": [
"$$this",
{
mydictionary: {
"$objectToArray": "$$this.mydictionary"
}
}
]
}
}
}
}
},
// convert the nested objects to array
{
$addFields: {
myarray: {
"$map": {
"input": "$myarray",
"as": "m",
"in": {
"$mergeObjects": [
"$$m",
{
"$cond": {
"if": {
$and: [
{
$eq: [
"$$m.key",
1
]
},
{
$in: [
"PointB",
"$$m.mydictionary.k"
]
}
]
},
"then": {
k: 1,
mydictionary: {
"$map": {
"input": "$$m.mydictionary",
"as": "md",
"in": {
"$mergeObjects": [
"$$md",
{
"$cond": {
"if": {
$eq: [
"$$md.k",
"PointB"
]
},
"then": {
k:"PointB",
v: {
point: "The UPDATED element",
userId: "The UPDATED element"
}
},
"else": {}
}
}
]
}
}
}
},
"else": {
"$cond": {
"if": {
$eq: [
"$$m.key",
1
]
},
"then": {
mydictionary: {
"$concatArrays": [
"$$m.mydictionary",
[
{
"k": "PointB",
"v": {
point: "The upserted element",
userId: "The upserted element"
}
}
]
]
}
},
"else": {}
}
}
}
}
]
}
}
}
}
},
// convert back to object
{
"$addFields": {
"myarray": {
"$map": {
"input": "$myarray",
"in": {
"$mergeObjects": [
"$$this",
{
mydictionary: {
"$arrayToObject": "$$this.mydictionary"
}
}
]
}
}
}
}
}
],
{
multi: true
})
Объяснение:
Также, чтобы подтвердить ваш вопрос:
В MongoDB операция записи является атомарной на уровне одного документа, даже если операция изменяет несколько встроенных документов в одном документе. Для ситуаций, требующих атомарности чтения и записи нескольких документов (в одной или нескольких коллекциях), MongoDB поддерживает транзакции с несколькими документами.
атомарность в mongoDB гарантируется для одного документа, если вы не используете транзакции, поэтому да, обновление или обновление внутри массива будет атомарным, в примере я добавил обновление/вставку (multi:true) для двух документов, операция не будет атомарной для двух документов, но только для одного документа.
Кажется, это работает. Что делать, если элемент, содержащий точки, не существует, и мы хотим создать его (например, с ключом: 3) и сохранить новую точку в их словаре в той же операции? Насколько я знаю, $addToSet нельзя использовать, потому что это тот же путь. Пробовал с $addToFields, но безуспешно
Вот почему я добавил вам 2x документа в коллекцию примеров выше, чтобы проиллюстрировать 2x опции, в первом документе это update, так как PointB существует, во втором документе это upsert/addToSet (insert to the array = $concatAraays), так как PointB не существует...
Да, мой вопрос был больше о том, что если элемент в myarray не существует, и я хочу его создать, но я понял, как это сделать, просто добавьте для этого дополнительный этап addFields. Огромное спасибо за помощь.
Действительно, вы можете добавить еще один этап addFields в $concatArray:[], если ключ:1 отсутствует...
Давайте продолжим обсуждение в чате.
Спасибо. Будет ли это атомным?