繁体   English   中英

Mongoose:尝试从数组中删除最后一个元素会导致看似奇怪的行为

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM