编写Mongoose软删除插件:如何正确使用$concat修改唯一字段
Mongoose软删除插件:$concat操作符使用错误的解决办法
你的问题出在混淆了Mongoose实例操作和数据库更新操作的区别:this.save()是基于客户端内存中的文档实例执行保存,你直接给name字段赋值包含$concat的对象,而你的schema里name是字符串类型,Mongoose会尝试把这个对象转成字符串,自然抛出类型转换错误。
要使用$concat这类MongoDB数据库层面的操作符,你需要用数据库更新方法(比如updateOne)而非实例的save()方法,因为只有数据库更新操作才支持解析操作符。
解决方案1:使用原子更新(推荐)
直接调用文档实例的updateOne方法,在数据库层面完成软删除标记和字段重命名,这是原子操作,避免并发修改导致的问题:
schema.methods.delete = async function () { return this.updateOne({ $set: { deleted: true, name: { $concat: ["$name", "_deleted"] } } }); };
注意:$concat要求MongoDB版本3.2及以上,如果你的版本过低,需要升级或者用下面的客户端拼接方案。
解决方案2:客户端拼接后保存
如果不想用数据库操作符,可以先在客户端获取当前name值,拼接后缀后再保存:
schema.methods.delete = async function () { this.deleted = true; this.name = `${this.name}_deleted`; return this.save(); };
这种方式的缺点是:如果在你获取文档到执行save()的间隙,有其他请求修改了name字段,会覆盖掉对方的修改,安全性不如原子更新。
扩展优化:动态处理所有唯一字段
如果你的schema里有多个唯一字段,不想硬编码name,可以遍历schema的paths来自动识别唯一字段:
schema.methods.delete = async function () { const update = { $set: { deleted: true } }; // 遍历schema的所有字段,找出带unique约束的字符串字段 Object.keys(schema.paths).forEach(path => { const field = schema.paths[path]; if (field.options.unique && field.instance === 'String') { update.$set[path] = { $concat: [`$${path}`, "_deleted"] }; } }); return this.updateOne(update); };
内容的提问来源于stack exchange,提问作者Christof Aenderl
相关产品推荐
相关产品推荐

