假设我们有一个这样的模式:
const PageSchema = new mongoose.Schema({
content: String
order: Number
})
我们希望order
始终是0
和n-1
之间的唯一数字,其中n
是文档总数。
在插入或删除文档时如何确保这一点?
对于插入物,我目前使用此钩子:
PageSchema.pre('save', async function () {
if (!this.order) {
const lastPage = await this.constructor.findOne().sort({ order: -1 })
this.order = lastPage ? lastPage.order + 1 : 0
}
})
这似乎在插入新文档时起作用。
删除文档后,我必须减少order
较高的文档的order
。但是,我不确定删除文档时会调用哪个挂钩。
效率对我来说不是问题:插入和删除的次数不多。
如果我能以某种方式仅提供一个对整个集合进行迭代的功能,例如fix_order
,那完全可以。如何安装此功能,以便在插入或删除文档时调用它?
答案 0 :(得分:1)
您可以使用findOneAndDelete前后钩子来完成此操作。
正如您在pre findOneAndDelete挂钩中所看到的,我们保存了对已删除文档的引用,并将其传递给postfindOneAndDelete,以便我们可以使用构造函数访问模型,并使用updateMany方法调整订单。
PageSchema.pre("findOneAndDelete", async function(next) {
this.page = await this.findOne();
next();
});
PageSchema.post("findOneAndDelete", async function(doc, next) {
console.log(doc);
const result = await this.page.constructor.updateMany(
{ order: { $gt: doc.order } },
{
$inc: {
order: -1
}
}
);
console.log(result);
next();
});
假设您有以下3个文档:
[
{
"_id": ObjectId("5e830a6d0dec1443e82ad281"),
"content": "content1",
"order": 0,
"__v": 0
},
{
"_id": ObjectId("5e830a6d0dec1443e82ad282"),
"content": "content2",
"order": 1,
"__v": 0
},
{
"_id": ObjectId("5e830a6d0dec1443e82ad283"),
"content": "content3",
"order": 2,
"__v": 0
}
]
当您使用带有这样的findOneAndDelete方法的"_id": ObjectId("5e830a6d0dec1443e82ad282")
删除content2时:
router.delete("/pages/:id", async (req, res) => {
const result = await Page.findOneAndDelete({ _id: req.params.id });
res.send(result);
});
中间件将运行,并调整顺序,其余2个文档将如下所示:
[
{
"_id": ObjectId("5e830a6d0dec1443e82ad281"),
"content": "content1",
"order": 0,
"__v": 0
},
{
"_id": ObjectId("5e830a6d0dec1443e82ad283"),
"content": "content3",
"order": 1, => DECREASED FROM 2 to 1
"__v": 0
}
]
此外,您最好将下一个包含在预保存的中间件中,以便其他中间件也可以在以后添加时使用。
PageSchema.pre("save", async function(next) {
if (!this.order) {
const lastPage = await this.constructor.findOne().sort({ order: -1 });
this.order = lastPage ? lastPage.order + 1 : 0;
}
next();
});
答案 1 :(得分:0)
根据SuleymanSah的回答,我编写了一个猫鼬插件来完成这项工作。这样,它可以应用于多个架构,而无需重复代码。
它有两个可选参数:
path
:要存储序号的路径名(默认为order
)scope
:相对于应该给出数字的路径名或路径名数组(默认为[])示例。不应对章节进行全局编号,而应相对于其所属的书进行编号:
ChapterSchema.plugin(orderPlugin, { path: 'chapterNumber', scope: 'book' })
文件orderPlugin.js
:
function getConditions(doc, scope) {
return Object.fromEntries([].concat(scope).map((path) => [path, doc[path]]))
}
export default (schema, options) => {
const path = (options && options.path) || 'order'
const scope = (options && options.scope) || {}
schema.add({
[path]: Number,
})
schema.pre('save', async function () {
if (!this[path]) {
const last = await this.constructor
.findOne(getConditions(this, scope))
.sort({ [path]: -1 })
this[path] = last ? last[path] + 1 : 0
}
})
schema.post('findOneAndDelete', async function (doc) {
await this.model.updateMany(
{ [path]: { $gt: doc[path] }, ...getConditions(doc, scope) },
{ $inc: { [path]: -1 } }
)
})
}