【问题标题】:How to remove object taking into account references in Mongoose Node.js?考虑到 Mongoose Node.js 中的引用,如何删除对象?
【发布时间】:2016-10-26 22:27:35
【问题描述】:

这是我的 MongoDB 架构:

var partnerSchema = new mongoose.Schema({
    name: String,
    products: [
        {
            type: mongoose.Schema.Types.ObjectId,
            ref: 'Product'
        }]
});

var productSchema = new mongoose.Schema({
    name: String,
    campaign: [
        {
            type: mongoose.Schema.Types.ObjectId,
            ref: 'Campaign'
        }
    ]
});

var campaignSchema = new mongoose.Schema({
    name: String,
});


module.exports = {
    Partner: mongoose.model('Partner', partnerSchema),
    Product: mongoose.model('Product', productSchema),
    Campaign: mongoose.model('Campaign', campaignSchema)
}

我想知道如何在考虑到引用的情况下从任何文档中删除对象(也许我应该以某种方式从猫鼬中填充)?例如,如果我将删除 Product,那么我假设我还将删除 Partner 中的引用 ID 以及属于此 Product 的所有 Campaigns

目前我以这种方式删除:

var campSchema = require('../model/camp-schema');

    router.post('/removeProduct', function (req, res) {
            campSchema.Product.findOneAndRemove({ _id: req.body.productId }, function (err, response) {
                if (err) throw err;
                res.json(response);
            });
        });

但是在 mongo 中仍然留下了引用。

【问题讨论】:

    标签: node.js mongodb mongoose mongoose-populate


    【解决方案1】:

    您必须嵌套调用才能从其他模型中删除产品 ID。例如,在您从Product 中删除产品的电话中 集合,您还可以再次调用以从结果回调中的Partner 模型中删除引用。默认情况下删除产品将删除其对Campaign 模型的引用。

    下面的代码展示了上面的直觉:

    var campSchema = require('../model/camp-schema');
    
    router.post('/removeProduct', function (req, res) {
        campSchema.Product.findOneAndRemove({ _id: req.body.productId }, function (err, response) {
            if (err) throw err;
            campSchema.Partner.update(
                { "products": req.body.productId },
                { "$pull": { "products": req.body.productId } },
                function (err, res){
                    if (err) throw err;
                    res.json(res);
                }
            );
        });
    });
    

    要删除关联的广告系列,您可能需要额外的删除操作,从给定的产品 ID 中获取关联的广告系列 ID。考虑一下以下肮脏的技巧,如果不小心回调嵌套,它可能会给你一张到callback hell 的单程票:

    router.post('/removeProduct', function (req, res) {
        campSchema.Product.findOneAndRemove(
            { _id: req.body.productId }, 
            { new: true },
            function (err, product) {
                if (err) throw err;
                campSchema.Partner.update(
                    { "products": req.body.productId },
                    { "$pull": { "products": req.body.productId } },
                    function (err, res){
                        if (err) throw err;
                        var campaignList = product.campaign
                        campSchema.Campaign.remove({ "_id": { "$in": campaignList } })
                                    .exec(function (err, res){
                                        if (err) throw err;
                                        res.json(product);
                                    })
                    }
                );
            }
        );
    });
    

    虽然它有效,但可以通过使用 async/await 或 async 库来避免上述潜在陷阱。但首先,为了让您更好地理解在 async 模块中使用多个回调,让我们通过一个来自Seven Things You Should Stop Doing with Node.js 的示例来说明这一点,该示例使用回调来查找父实体的多个操作,然后查找属于父级的子实体:

    methodA(function(a){
        methodB(function(b){
            methodC(function(c){
                methodD(function(d){
                    // Final callback code        
                })
            })
        })
    })
    

    使用 async/await,您的调用将被重组为

    router.post('/removeProduct', async (req, res) => {
        try {
            const product = await campSchema.Product.findOneAndRemove(
                { _id: req.body.productId }, 
                { new: true }
            )
    
            await campSchema.Partner.update(
                { "products": req.body.productId },
                { "$pull": { "products": req.body.productId } }
            )
    
            await campSchema.Campaign.remove({ "_id": { "$in": product.campaign } })
    
            res.json(product)
        } catch(err) {
            throw err
        }
    })
    

    使用异步模块,您可以使用系列方法来解决使用回调来嵌套多个方法的代码,这可能会导致Callback Hell

    Series:

    async.series([
        function(callback){
            // code a
            callback(null, 'a')
        },
        function(callback){
            // code b
            callback(null, 'b')
        },
        function(callback){
            // code c
            callback(null, 'c')
        },
        function(callback){
            // code d
            callback(null, 'd')
        }],
        // optional callback
        function(err, results){
            // results is ['a', 'b', 'c', 'd']
            // final callback code
        }
    )
    

    或者waterfall

    async.waterfall([
        function(callback){
            // code a
            callback(null, 'a', 'b')
        },
        function(arg1, arg2, callback){
            // arg1 is equals 'a' and arg2 is 'b'
            // Code c
            callback(null, 'c')
        },
        function(arg1, callback){      
            // arg1 is 'c'
            // code d
            callback(null, 'd');
        }], function (err, result) {
            // result is 'd'    
        }
    )
    

    现在回到您的代码,使用异步瀑布方法,您可以将代码重构为

    router.post('/removeProduct', function (req, res) {
        async.waterfall([
            function (callback) {
                // code a: Remove Product
                campSchema.Product.findOneAndRemove(
                    { _id: req.body.productId }, 
                    function (err, product) {
                        if (err) callback(err);
                        callback(null, product);
                    }
                );
            },
    
            function (doc, callback) {
                // code b: Remove associated campaigns
                var campaignList = doc.campaign;
                campSchema.Campaign
                    .remove({ "_id": { "$in": campaignList } })
                    .exec(function (err, res) {
                    if (err) callback(err);
                    callback(null, doc);
                }
                );
            },
    
            function (doc, callback) {
                // code c: Remove related partner
                campSchema.Partner.update(
                    { "products": doc._id },
                    { "$pull": { "products": doc._id } },
                    function (err, res) {
                        if (err) callback(err);
                        callback(null, doc);
                    }
                );
            }
        ], function (err, result) {
            if (err) throw err;
            res.json(result);  // OUTPUT OK
        });
    });
    

    【讨论】:

    • 我明白了。但在文档campaign 中仍然是分配给已移除产品的对象。我应该使用下一个回调删除他吗?
    • 哦,您还想删除与产品关联的广告系列吗?我以为你只是想从其他模型中删除产品及其引用,即它的关系,而不是删除与已删除产品相关的模型?
    • 那么,没有指定产品的活动是不必要的。所以我不应该把它保存在文档中。
    • 从设计上看似乎没有必要,为什么不将模式嵌入Product 模型中,而不必创建自己的单独模型?这是因为根据您的要求,Campaign 直接依赖于Product 模型,删除产品也意味着删除其广告系列,因此嵌入它比将其保留为单独的重新引用模型更有意义。
    • 您需要异步库吗?即var async = require('async');?
    猜你喜欢
    • 1970-01-01
    • 2016-10-28
    • 1970-01-01
    • 2018-12-30
    • 2011-08-14
    • 2013-11-29
    • 2016-09-16
    相关资源
    最近更新 更多