考虑到 Mongoose Node.js 中的引用,如何删除对象?

How to remove object taking into account references in Mongoose Node.js?

这是我的 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)
}

我想知道如何在考虑引用的情况下从任何文档中删除对象(也许我应该使用某种方式从 mongoose 中填充)?例如,如果我将删除 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 中仍然留下了参考资料。

您必须嵌套调用才能从其他模型中删除产品 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。考虑以下肮脏的 hack,如果不注意回调嵌套,它可能会奖励你一张 one-way 到 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 library. But firstly, to give you a better understanding of the using multiple callbacks with the async module, let's illustrate this with an example from Seven Things You Should Stop Doing with Node.js 多个带有回调的操作来查找 parent 实体,从而避免上述潜在陷阱,然后找到属于 parent:

的 child 个实体
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
    }
})

使用 async 模块,您可以使用 series 方法来解决使用回调来嵌套多个方法的代码,这可能会导致 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
    });
});