Mongoose 子文档预删除中间件未调用

Posted

技术标签:

【中文标题】Mongoose 子文档预删除中间件未调用【英文标题】:Mongoose sub document pre remove middleware not called 【发布时间】:2015-11-08 14:43:40 【问题描述】:

我会阻止任何子文档被删除,因此我在每个子文档架构的 pre('remove') 中间件中添加了一个错误。

当调用 .remove() 函数时,它实际上调用了中间件。但是当它被删除而不调用remove()时,中间件不会检查它是否已经被删除。

重要的情况是当我从远程源接收对象时,我想通过 mongoose 中间件执行所有完整性检查,以将所有内容保持在同一个位置。无论是否错误,远程源都可能删除了其中一个子文档。所以当Mongoose检查整个文档时,子文档已经被删除了,没有触发.remove()函数。

这是我的问题的最小工作示例:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var subDateSchema = new Schema(
    date_add: type: Date, default: Date.now,
    date_remove: type: Date, default: null
);

var ResourceSchema = new Schema(
    activation_dates: [subDateSchema]
);

subDateSchema.pre('remove', function(next)
    next(new Error("YOU CAN'T DELETE ANY ACTIVATION DATE"));
);

var Resource = mongoose.model('Resource', ResourceSchema);

var newresource = new Resource(
    activation_dates: [
        date_add: Date.now()
    ]
);

newresource.save(function(err)
    if(err) throw err;
    newresource.activation_dates.splice(0, 1);
    /**
      * Here I tried
      * newresource.markModified('activation_dates');
      * On update it *DOES* trigger pre save and pre validate
      * But it does nothing to deleted content
    **/ 
    newresource.save(function(err)
        if(err) throw err;
    );
);

所以我的问题是:有没有一种干净的方法来调用子文档删除中间件,而无需继续检查所有以前的元素并与新元素进行比较以查看哪些元素被删除?

【问题讨论】:

“子文档”,因此“数组成员”从不按照动作钩子的建议“删除”。它们只会从阵列中“拉出”。这就是您的代码失败的原因。 @BlakesSeven 好吧,有没有办法检查从数组中提取的子文档? 仔细想想,其实我觉得猫鼬模型根本不支持这个。甚至在 .update() 或类似的“原子”操作(例如 $pull)上“支持”“验证”过程的能力在代码库中也是一个非常“新”的东西。因此,我建议“验证”挂钩而不是 .pre() 中间件更合适。我自己还没有尝试过你的过程。周末可以试试。 【参考方案1】:

经过一番研究,我发现了这个:

一种解决方法是将事件挂钩到整个子文档数组,并拥有前一个数据数组的副本。

这是一个完整的工作示例,说明如何确保数组元素未被删除或拉出。要检查修改,您需要进一步修改。

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var ResourceSchema = new Schema(
    activation_dates: [subDateSchema]
);

// This virtual permits to store the original array accross the middlewares
ResourceSchema.virtual("original").set(function(item)
    this._original = item;
).get(function()
    return this._original;
);

// This middleware checks for a previous version of the "Resource"
ResourceSchema.pre("validate", function(next)
    var self = this;
    mongoose.model("Resource").findById(this._id, function(err, doc)
        if(err) throw err;
        self.original = doc;
        next();
    );
);

// This validation block checks for any modification of the array of sub documents
ResourceSchema.path("activation_dates").validate(function(value)
    var j;
    if(this.original)
        // if the new array is smaller than the original, no need to go further
        if(this.original.activation_dates.length > value.length)
            return false;
        

        for(var i=0; i < this.original.activation_dates.length; i++)
            j=0;
            // if the array element has been deleted but not pulled out, we need to check it
            if(typeof value[j] == "undefined" || typeof value[j]._id == "undefined")
                return false;
            

            while(value.length > j && this.original.activation_dates[i]._id.toString() != value[j]._id.toString())
                j++;
            
            if(j == value.length)
                return false;
            
        
    
    return true;
, "You deleted at least one element of the array");

var Resource = mongoose.model('Resource', ResourceSchema);

var newresource = new Resource(
    activation_dates: [
        date_add: Date.now()
    ]
);

newresource.save(function(err)
    if(err) throw err;

    newresource.activation_dates.splice(0, 1);
    // OR:
    //delete newresource.activation_dates[0];
    // this line is essential in the case you *delete* but not pull out
    newresource.markModified('activation_dates');

    newresource.save(function(err)
        if(err) throw err;
    );
);

不幸的是,除了对所有元素进行循环并检索原始文档之外,我找不到其他解决方案。

【讨论】:

以上是关于Mongoose 子文档预删除中间件未调用的主要内容,如果未能解决你的问题,请参考以下文章

为啥不调用带有 query: true 的 mongoose 中间件“pre”remove?

Typescript Mongoose - 方法/静态函数未被调用 [Kubernetes - Docker]

Mongoose GeoJSON 在使用中间件时抛出“MongoError:无法提取地理键”

Mongoose pre.save() 异步中间件未按预期工作

Mongoose pre.save() 异步中间件未按预期工作

nodejs 中使用mysql数据有没有类似 mongoose 的中间件