Я уже некоторое время пытаюсь запустить это, но не могу понять, что я делаю неправильно.
У меня есть две схемы, подобные этой
const paymentSchema = new Schema({
year_month: {
type: String,
required: true
},
status: {
type: Boolean,
required: true
}
});
const testSchema = new Schema({
name: {
type: String,
required: true
},
payments: [{
type: paymentSchema,
required: false,
}]
});
Затем я хочу обновить существующее значение или, если это значение недоступно, я бы хотел добавить его в массив.
Допустим, у меня есть эти значения в БД:
[
{
"_id": "5e90ae0e0ed9974174e92826",
"name": "User 1",
"payments": [
{
"_id": "5e90c3fb79bba9571ae58a66",
"year_month": "2020_02",
"status": false
}
]
}
]
Теперь я хотел бы изменить статус year_month 2020_02 на true с этим кодом, и он работает:
testSchema.findOneAndUpdate(
{
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_02' }}
},
{ $set: {
'payments.$': {
year_month: '2020_02',
status: false
}
}
},
{
new: true,
upsert: true
}
).then( result => {
response.send(result);
});
Проблема появляется, когда я пытаюсь сделать это
testSchema.findOneAndUpdate(
{
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_03' }}
},
{ $set: {
'payments.$': {
year_month: '2020_03',
status: false
}
},
},
{
new: true,
upsert: true
}
).then( result => {
response.send(result);
});
Я получаю это сообщение от upsert ...
(node:8481) UnhandledPromiseRejectionWarning: MongoError: The positional operator did not find the match needed from the query.
at Connection.<anonymous> (/home/vedran/Documents/Projekt/node_modules/mongodb/lib/core/connection/pool.js:466:61)
at Connection.emit (events.js:223:5)
at Connection.EventEmitter.emit (domain.js:475:20)
at processMessage (/home/vedran/Documents/Projekt/node_modules/mongodb/lib/core/connection/connection.js:384:10)
at TLSSocket.<anonymous> (/home/vedran/Documents/Projekt/node_modules/mongodb/lib/core/connection/connection.js:553:15)
at TLSSocket.emit (events.js:223:5)
at TLSSocket.EventEmitter.emit (domain.js:475:20)
at addChunk (_stream_readable.js:309:12)
at readableAddChunk (_stream_readable.js:290:11)
at TLSSocket.Readable.push (_stream_readable.js:224:10)
at TLSWrap.onStreamRead (internal/stream_base_commons.js:181:23)
(node:8481) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:8481) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Согласно документации Пн goose .findOneAndUpdate () это должно работать, но я делаю некоторую ошибку, и я не могу понять, что именно. Я знаю, что запрос на совпадение - это проблема, но я не уверен, как изменить его, чтобы применить апсерт.
В конце я решил это так:
testSchema.findOneAndUpdate(
{
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_03' }}
},
{
$set: {
'payments.$': {
year_month: '2020_02',
status: false
}
}
},
{new: true}
).then( success => {
// response === null if no match is found
if( success ) {
response.send(success);
} else {
testSchema.findOneAndUpdate(
{ _id: '5e90ae0e0ed9974174e92826' },
{
$push: {
'payments': request.body
}
},
{new: true}
).then(success => {
response.send(success);
});
}
},
error => {
response.send(error);
}
);
Но я Я делаю два запроса здесь, которые могут вызвать проблемы с состоянием гонки. 1. обновить и 2. добавить, если он не существует
Я хотел бы знать, есть ли лучший способ заставить его использовать upsert и избегать условий гонки.
Существует также хорошее короткое руководство по mon goose page , в котором описывается upsert для findOneAndUpdate, но оно не включает массивы, и это, вероятно, и осложняет проблему в моем случае.
Окончательное решение, основанное на ответах Джо и Прасада. На самом деле все не так сложно, если вы потратите время, чтобы понять, что здесь происходит.
testSchema.findOneAndUpdate(
{ "_id": customerId },
[{
$set: {
payments: {
$cond: [
{
$gt: [
{
$size: {
$filter: {
input: "$payments",
cond: {
$eq: [
"$$this.year_month",
testData.payments.year_month
]
}
}
}
},
0
]
},
{
$reduce: {
input: "$payments",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[{
$cond: [
{ $eq: ["$$this.year_month", testData.payments.year_month] },
{ $mergeObjects: ["$$this", { status: testData.payments.status }] },
"$$this"
]
}]
]
}
}
},
{
$concatArrays: [
"$payments",
[testData.payments]
]
}
]
}
}
}],
{ new: true }
).then(
success => {
response.send(success);
},
error => {
response.send(error);
}
);