[英]Mongoose: attempting to remove last element from an array results in seemingly bizarre behavior
Update: I switched from updateOne and $pull to simply filtering the array and saving it. I don't know why but that solves the first issue of html elements being removed. The same error occurs when deleting the last Item in a Menu however.
我有以下 Express 路由器,用于从菜单中的数组中删除 Item 元素:
router.put('/menus/:menuid/items/:itemid', async (req, res, next) => {
console.log('attempting to delete menu item...')
const menu = await Menu.findById(req.params.menuid)
const item = menu.items.find(item => item.id === req.params.itemid)
console.log(item)
console.log('updating...')
const response = await Menu.updateOne(
{ _id: menu._id },
{ $pull: { items: { _id: item._id } } }
)
console.log(response)
req.menu = await Menu.findById(req.params.menuid)
})
这成功地删除了所需的数组元素,但是在此请求上调用 fetch() 的 function 不会继续执行 then(); 在我刷新浏览器之前,页面上的元素不会改变:
function redirectThenDeleteElement(path, id, method) {
console.log("redirect to " + path + " using " + method)
fetch(path, { method: method })
.then(response => {
if (response.ok) {
console.log('fetch successful')
const itemToDelete = document.getElementById(id)
itemToDelete.remove()
} else {
console.log('error with request')
}
}).catch(error => {
console.error('error with fetch call: \n' + error)
})
}
从这里开始变得更奇怪了。 如果我从菜单中删除最后一项,它会按预期运行。 如果我添加一个新项目然后再次删除它,也是如此。 但是,如果我从一个菜单中删除所有项目,然后从另一个菜单中删除最后一个项目,则会记录“正在更新...”并且我收到错误: MongoServerError: E11000 duplicate key error collection: pfa-site.menus index: items.title_1 dup key: { items.title: null }
如果我用空项目数组为一个菜单播种,然后从另一个菜单中删除最后一项,也会发生这种情况。 它指的是 items.title_dup/items.title,我不知道为什么会这样,而且我不知道索引在这种情况下意味着什么。
我的第一个想法是,如果 items.title 是 Item 的唯一标题属性,如果多个菜单出于某种原因试图将其 items.title 属性更新为 null,则错误是有意义的。但结果是如果我从架构中删除唯一参数,则相同:
const menuItemSchema = new mongoose.Schema({
title: {
type: String,
required: true,
unique: false
},
content: String,
sanitizedHtml: {
type: String,
required: true
},
slug: {
type: String,
required: true,
unique: true
}
})
const menuSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
order: {
type: Number,
required: true
},
items: {
type: [menuItemSchema]
},
slug: {
type: String,
required: true,
unique: true
}
})
menuSchema.pre('validate', function(next) {
if (this.title) {
this.slug = slugify(this.title, { lower: true, strict: true })
}
this.items.forEach((item) => {
console.log('content being sanitized...')
if (item.content) {
item.sanitizedHtml = dompurify.sanitize(marked.parse(item.content))
}
if (item.title) {
item.slug = slugify(item.title, { lower: true, strict: true })
}
})
next()
})
module.exports = mongoose.model('Menu', menuSchema)
也许最奇怪的部分是,如果我向 updateOne 添加一个回调 function,它只记录任何错误/结果,然后尝试在任何情况下删除任何项目,我都会收到错误: MongooseError: Query was already executed: Menu.updateOne({ _id: new ObjectId("63b3737f6d748ace63beef8a... at model.Query._wrappedThunk [as _updateOne]
这是添加了回调的代码:
router.patch('/menus/:menuid/items/:itemid', async (req, res, next) => {
console.log('attempting to delete menu item...')
const menu = await Menu.findById(req.params.menuid)
const item = menu.items.find(item => item.id === req.params.itemid)
console.log(item)
console.log('updating...')
const response = await Menu.updateOne(
{ _id: menu._id },
{ $pull: { items: { _id: item._id } } },
function(error, result) {
if (error) {
console.log(error)
} else {
console.log(result)
}
}
)
console.log(response)
req.menu = await Menu.findById(req.params.menuid)
})
感谢您花时间阅读所有这些内容。 你是一个真实的人,我希望你知道发生了什么!
我试过使用 findOneAndUpdate 而不是 updateOne 并使用不同的 http 请求方法,以及其他失败的事情,我忘记了。 由于我认为不相关的原因,结果相似或损坏。
我认为这与您处理请求的方式有关。 作为更好更简洁的代码,我建议将“put”方法设为“delete”,这样 api 将明确用于从菜单中删除项目。 另外,我不知道结果被发送回前端的位置。 我会建议这样的事情:
router.delete('/menus/:menuid/items/:itemid', async (req, res, next) => {
console.log('attempting to delete menu item...')
const {menuid, itemid} = req.params;
const menu = await Menu.findById(menuid)
const item = menu.items.find(item => item.id === itemid)
if(!item){
throw new Error('item not found')
}
console.log(item)
console.log('updating...')
const response = await Menu.updateOne(
{ _id: menu._id },
{ $pull: { items: { _id: itemid } } },
{ new: true}
)
console.log(response)
return res.status(200).json(response);
})
解决方案:事实证明,由于 Item 模式具有唯一字段,MongoDB 会自动将索引添加到父模式,其中索引名称为 title_1,key:value 在项目数组为空的情况下为“items.title” : null. 因此,当从第二个菜单中删除最后一个项目时,在有重复索引的地方会发生一些冲突吗? 我不确定,所以对此的任何澄清都会有所帮助。
无论如何,解决方案是在 unique: true 之外添加 sparse: true。 这直接防止索引具有 null 值。 再一次,进一步澄清这是如何工作的会很好。 有关详细信息,请参阅 BahaEddine Ayadi 的评论。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.