这当然取决于您对它的预期用途,但我会说总体上不需要“插件”。您正在寻找的基本功能已经通过"upserts"“内置”到 MongoDB 中。
根据定义,只要使用集合的“唯一键”发出“选择”文档的查询条件,“更新插入”就不会产生“重复键错误”。在这种情况下"name"。
简而言之,您只需执行以下操作即可模仿与上述相同的行为:
let results = await Promise.all([
Pokemon.findOneAndUpdate({ "name": "Pikachu" },{},{ "upsert": true, "new": true }),
Pokemon.findOneAndUpdate({ "name": "Pikachu" },{},{ "upsert": true, "new": true }),
Pokemon.findOneAndUpdate({ "name": "Pikachu" },{},{ "upsert": true, "new": true })
]);
这将在第一次调用时简单地“创建”它不存在的项目,或者“返回”现有项目。这就是“upsert”的工作原理。
[
{
"_id": "5a022f48edca148094f30e8c",
"name": "Pikachu",
"__v": 0
},
{
"_id": "5a022f48edca148094f30e8c",
"name": "Pikachu",
"__v": 0
},
{
"_id": "5a022f48edca148094f30e8c",
"name": "Pikachu",
"__v": 0
}
]
如果您真的不关心“返回”每个调用,而只是想“更新或创建”,那么使用bulkWrite() 发送一个请求实际上要高效得多:
// Issue a "batch" in Bulk
let result = await Pokemon.bulkWrite(
Array(3).fill(1).map( (e,i) => ({
"updateOne": {
"filter": { "name": "Pikachu" },
"update": {
"$set": { "skill": i }
},
"upsert": true
}
}))
);
因此,您不必等待服务器解决三个异步调用,而只需创建 一个,它可以“创建”项目或“更新”您在找到时在 $set 修饰符中使用的任何内容。这些适用于每场比赛,包括第一场比赛,如果您想“仅在创建时”,$setOnInsert 可以做到这一点。
当然这只是一个“写”,所以这真的取决于你是否返回修改后的文档是否重要。因此,“批量”操作只是“写入”并且它们不会返回,而是返回有关“批次”的信息,指示“更新”和“修改”的内容,如下所示:
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 1, // <-- created 1 time
"nMatched": 2, // <-- matched and modified the two other times
"nModified": 2,
"nRemoved": 0,
"upserted": [
{
"index": 0,
"_id": "5a02328eedca148094f30f33" // <-- this is the _id created in upsert
}
],
"lastOp": {
"ts": "6485801998833680390",
"t": 23
}
}
因此,如果您确实想要“返回”,那么更典型的情况是在“创建”时将您想要的数据与“更新”时需要哪些数据分开。请注意,$setOnInsert 本质上是“暗示”选择文档的“查询”条件中的任何值:
// Issue 3 pokemon as separate calls
let sequence = await Promise.all(
Array(3).fill(1).map( (e,i) =>
Pokemon.findOneAndUpdate(
{ name: "Pikachu" },
{ "$set": { "skill": i } },
{ "upsert": true, "new": true }
)
)
);
这将显示在每个原子事务的“序列”中应用的修改:
[
{
"_id": "5a02328fedca148094f30f38",
"name": "Pikachu",
"__v": 0,
"skill": 0
},
{
"_id": "5a02328fedca148094f30f39",
"name": "Pikachu",
"__v": 0,
"skill": 1
},
{
"_id": "5a02328fedca148094f30f38",
"name": "Pikachu",
"__v": 0,
"skill": 2
}
]
因此,通常这里是您想要的“更新插入”,根据您的意图,您可以使用单独的调用来返回每个修改/创建,或者批量发出“写入”。
作为演示以上所有内容的完整清单:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const pokemonSchema = new Schema({
name: String,
skill: Number
},{ autoIndex: false });
pokemonSchema.index({ name: 1 },{ unique: true, background: false });
const Pokemon = mongoose.model('Pokemon', pokemonSchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Await index creation, otherwise we error
await Pokemon.ensureIndexes();
// Clean data for test
await Pokemon.remove();
// Issue 3 pokemon as separate calls
let pokemon = await Promise.all(
Array(3).fill(1).map( e =>
Pokemon.findOneAndUpdate({ name: "Pikachu" },{},{ "upsert": true, "new": true })
)
);
log(pokemon);
// Clean data again
await Pokemon.remove();
// Issue a "batch" in Bulk
let result = await Pokemon.bulkWrite(
Array(3).fill(1).map( (e,i) => ({
"updateOne": {
"filter": { "name": "Pikachu" },
"update": {
"$set": { "skill": i }
},
"upsert": true
}
}))
);
log(result);
let allPokemon = await Pokemon.find();
log(allPokemon);
// Clean data again
await Pokemon.remove();
// Issue 3 pokemon as separate calls
let sequence = await Promise.all(
Array(3).fill(1).map( (e,i) =>
Pokemon.findOneAndUpdate(
{ name: "Pikachu" },
{ "$set": { "skill": i } },
{ "upsert": true, "new": true }
)
)
);
log(sequence);
} catch(e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
这会产生输出(对于那些懒得自己运行的人):
Mongoose: pokemons.ensureIndex({ name: 1 }, { unique: true, background: false })
Mongoose: pokemons.remove({}, {})
Mongoose: pokemons.findAndModify({ name: 'Pikachu' }, [], { '$setOnInsert': { __v: 0 } }, { upsert: true, new: true, remove: false, fields: {} })
Mongoose: pokemons.findAndModify({ name: 'Pikachu' }, [], { '$setOnInsert': { __v: 0 } }, { upsert: true, new: true, remove: false, fields: {} })
Mongoose: pokemons.findAndModify({ name: 'Pikachu' }, [], { '$setOnInsert': { __v: 0 } }, { upsert: true, new: true, remove: false, fields: {} })
[
{
"_id": "5a023461edca148094f30f82",
"name": "Pikachu",
"__v": 0
},
{
"_id": "5a023461edca148094f30f82",
"name": "Pikachu",
"__v": 0
},
{
"_id": "5a023461edca148094f30f82",
"name": "Pikachu",
"__v": 0
}
]
Mongoose: pokemons.remove({}, {})
Mongoose: pokemons.bulkWrite([ { updateOne: { filter: { name: 'Pikachu' }, update: { '$set': { skill: 0 } }, upsert: true } }, { updateOne: { filter: { name: 'Pikachu' }, update: { '$set': { skill: 1 } }, upsert: true } }, { updateOne: { filter: { name: 'Pikachu' }, update: { '$set': { skill: 2 } }, upsert: true } } ], {})
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 1,
"nMatched": 2,
"nModified": 2,
"nRemoved": 0,
"upserted": [
{
"index": 0,
"_id": "5a023461edca148094f30f87"
}
],
"lastOp": {
"ts": "6485804004583407623",
"t": 23
}
}
Mongoose: pokemons.find({}, { fields: {} })
[
{
"_id": "5a023461edca148094f30f87",
"name": "Pikachu",
"skill": 2
}
]
Mongoose: pokemons.remove({}, {})
Mongoose: pokemons.findAndModify({ name: 'Pikachu' }, [], { '$setOnInsert': { __v: 0 }, '$set': { skill: 0 } }, { upsert: true, new: true, remove: false, fields: {} })
Mongoose: pokemons.findAndModify({ name: 'Pikachu' }, [], { '$setOnInsert': { __v: 0 }, '$set': { skill: 1 } }, { upsert: true, new: true, remove: false, fields: {} })
Mongoose: pokemons.findAndModify({ name: 'Pikachu' }, [], { '$setOnInsert': { __v: 0 }, '$set': { skill: 2 } }, { upsert: true, new: true, remove: false, fields: {} })
[
{
"_id": "5a023461edca148094f30f8b",
"name": "Pikachu",
"__v": 0,
"skill": 0
},
{
"_id": "5a023461edca148094f30f8b",
"name": "Pikachu",
"__v": 0,
"skill": 1
},
{
"_id": "5a023461edca148094f30f8b",
"name": "Pikachu",
"__v": 0,
"skill": 2
}
]
注意为了应用__v 键,$setOnInsert 也在所有“猫鼬”操作中“隐含”。因此,除非您将其关闭,否则该语句始终与发出的任何内容“合并”,因此允许在第一个示例“更新”块中使用 {},由于未应用更新修饰符,这将是核心驱动程序中的错误,然而 mongoose 为你添加了这个。
还要注意bulkWrite() 实际上并没有引用模型的“模式”并绕过它。这就是为什么在那些已发布的更新中没有__v 的原因,它确实也绕过了所有验证。这通常不是问题,但您应该注意这一点。