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

Posted

技术标签:

【中文标题】考虑到 Mongoose Node.js 中的引用,如何删除对象?【英文标题】:How to remove object taking into account references in 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 中仍然留下了引用。

【问题讨论】:

【参考方案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');?

以上是关于考虑到 Mongoose Node.js 中的引用,如何删除对象?的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 mongoose、Node.js 将数据插入到 mongoDB 中的 Document 字段中

使用 Node.js 和 Mongoose 将查询结果保存到模型/模式的属性

如何使用 Node.js 和 Mongoose 将对象添加到嵌套数组

Node.js Mongoose.js 字符串到 ObjectId 函数

Node.js Mongoose.js 字符串到 ObjectId 函数

Node.js Mongoose.js 字符串到 ObjectId 函数