Как определить, действительно ли $addToSet добавил новый элемент в документ MongoDB или элемент уже существует?
Я использую драйвер C# (v1.8.3 от NuGet), и мне трудно определить, является ли $addtoSet/upsert
Операция фактически добавила новый элемент в данный массив, или, если элемент уже существовал.
Добавление нового элемента может попасть в два случая: либо документ вообще не существует, а просто создан программой upsert, либо документ существует, но массив не существует или не содержит данный элемент.
Причина, по которой мне нужно это сделать, заключается в том, что у меня есть большие наборы данных для загрузки в MongoDB, которые могут (не должны, но могут) ломаться во время обработки. Если это произойдет, я должен иметь возможность начать резервное копирование с самого начала, не выполняя дублирующую последующую обработку (продолжайте обработку идемпотентом). В моем потоке, если определено, что элемент был добавлен заново, я ставлю в очередь процесс обработки этого элемента в нисходящем направлении, если определено, что он уже был добавлен в документ, то дальнейшая работа в нисходящем направлении не требуется. Моя проблема в том, что результат всегда возвращает сообщение о том, что вызов изменил один документ, даже если элемент уже существовал в массиве, и на самом деле ничего не было изменено.
Исходя из моего понимания API C# драйвера, я должен быть в состоянии сделать звонок с WriteConcern.Acknowledged
, а затем проверьте WriteConcernResult.DocumentsAffected
чтобы увидеть, действительно ли он обновил документ или нет.
Моя проблема заключается в том, что во всех случаях результат беспокойства о записи возвращает тот документ, который был обновлен.:/
Вот пример документа, который вызывает мой код $addToSet
on, который может иметь или не иметь этот конкретный элемент в списке "items" для начала:
{
"_id" : "some-id-that-we-know-wont-change",
"items" : [
{
"s" : 4,
"i" : "some-value-we-know-is-static",
}
]
}
Мой запрос всегда использует _id
значение, которое известно на основе метаданных обработки:
var query = new QueryDocument
{
{"_id", "some-id-that-we-know-wont-change"}
};
Мое обновление выглядит следующим образом:
var result = mongoCollection.Update(query, new UpdateDocument()
{
{
"$addToSet", new BsonDocument()
{
{ "items", new BsonDocument()
{
{ "s", 4 },
{ "i", "some-value-we-know-is-static" }
}
}
}
}
}, new MongoUpdateOptions() { Flags = UpdateFlags.Upsert, WriteConcern = WriteConcern.Acknowledged });
if(result.DocumentsAffected > 0 || result.UpdatedExisting)
{
//DO SOME POST PROCESSING WORK THAT SHOULD ONLY HAPPEN ONCE PER ITEM
}
Если я запускаю этот код один раз для пустой коллекции, документ добавляется, и ответ такой, как ожидалось (DocumentsAffected = 1
, UpdatedExisting = false
). Если я запустлю его снова (любое количество раз), документ не будет обновлен, поскольку он останется неизменным, но результат теперь неожиданный (DocumentsAffected = 1
, UpdatedExisting = true
).
Разве это не должно возвращаться DocumentsAffected = 0
если документ не изменился?
Поскольку нам нужно делать много миллионов таких вызовов в день, я не решаюсь превратить эту логику в несколько вызовов на элемент (сначала проверяя, существует ли элемент в указанном массиве документов, а затем добавляя / ставя в очередь или просто пропуская), если в все возможно.
Есть ли способ заставить это работать за один звонок?
1 ответ
Конечно, здесь вы фактически проверяете ответ, который указывает, был ли документ обновлен или вставлен, или фактически, если ни одна из операций не произошла. Это ваш лучший показатель для $addToSet
чтобы выполнить обновление, документ будет обновлен.
$addToSet
Сам оператор не может создавать дубликаты, такова природа оператора. Но у вас действительно могут быть некоторые проблемы с вашей логикой:
{
"$addToSet", new BsonDocument()
{
{ "items", new BsonDocument()
{
{ "id", item.Id },
{ "v", item.Value }
}
}
}
}
Ясно, что вы показываете, что элемент в вашем "наборе" состоит из двух полей, поэтому, если этот контент изменяется каким-либо образом (то есть идентичным идентификатором, но разным значением), тогда этот элемент фактически является "уникальным" членом набора и будет быть добавленным Там не будет никакого способа, например, для $addToSet
Оператор не добавляет новые значения исключительно на основе "id" в качестве уникального идентификатора. Вы должны были бы фактически свернуть это в коде.
Вторая возможность здесь для формы дубликата состоит в том, что ваша часть запроса неправильно находит документ, который должен быть обновлен. Результатом этого будет создание нового документа, который содержит только новый указанный элемент в "наборе". Таким образом, распространенная ошибка использования выглядит примерно так:
db.collection.update(
{
"id": ABC,
"items": { "$elemMatch": {
"id": 123, "v": 10
}},
{
"$addToSet": {
"items": {
"id": 123, "v": 10
}
}
},
{ "upsert": true }
)
Результатом такой операции всегда будет создание нового документа, поскольку существующий документ не содержит указанный элемент в "наборе". Правильная реализация - не проверять наличие элемента "set" и разрешать $addToSet
делать работу.
Если действительно у вас действительно есть дублирующиеся записи, встречающиеся в "множестве", где все элементы вложенного документа в точности совпадают, то это было вызвано каким-то другим кодом, существующим или в прошлом.
Если вы уверены, что создаются новые записи, просмотрите код на наличие $push
или действительно, и манипулирование массивами в коде, который, кажется, действует в том же поле.
Но если вы используете оператор правильно, то $addToSet
делает именно то, для чего предназначен.