Mongoose无更新pre hook?需实现编辑文档名称重复校验
Great question! I ran into this exact issue with Mongoose hooks a while back—those pre('save') hooks only fire for save() and create() calls, not for update operations like findByIdAndUpdate() or updateOne(). The good news is we can refactor your existing validation logic into a reusable function and hook it up to Mongoose's update-specific middleware.
Step 1: Extract Reusable Validation Logic
First, let's pull your name uniqueness check into a standalone async function. This fixes the this context problem in your original callback-based code and makes the logic usable across both save and update hooks:
async function validateUniqueCompanyName(doc, updateName) { // Use the updated name if provided, otherwise fall back to the document's current name const nameToCheck = updateName || doc.name; // Find companies with the same name, excluding the current document to avoid false positives const existingCompany = await this.model('Company').findOne({ name: nameToCheck, _id: { $ne: doc._id } }); if (existingCompany) { throw new Error("Company Name exists!"); } }
Step 2: Update the Pre-Save Hook
Modify your existing pre('save') hook to use this reusable function. We'll use async/await for cleaner error handling instead of nested callbacks:
company_model.companySchema.pre('save', async function(next) { try { // Pass the current document to the validation function await validateUniqueCompanyName.call(this, this); next(); } catch (err) { console.error('Error in pre-save validation:', err.message); next(err); // Pass the error to Mongoose to trigger a failure } });
Step 3: Add Hooks for Update Operations
Mongoose has separate middleware for update operations. Below are hooks for findOneAndUpdate (covers findByIdAndUpdate, which is common in API endpoints) and updateOne:
Pre-FindOneAndUpdate Hook
company_model.companySchema.pre('findOneAndUpdate', async function(next) { try { // Get the update payload from the query const update = this.getUpdate(); const updatedName = update.name; // Skip validation if the name isn't being modified if (!updatedName) { return next(); } // Fetch the current document to exclude it from the uniqueness check const currentDoc = await this.model('Company').findOne(this.getQuery()); if (!currentDoc) { return next(new Error('Company not found')); } // Run the validation with the updated name await validateUniqueCompanyName.call(this, currentDoc, updatedName); next(); } catch (err) { console.error('Error in pre-findOneAndUpdate validation:', err.message); next(err); } });
Pre-UpdateOne Hook (for batch or direct update calls)
company_model.companySchema.pre('updateOne', async function(next) { try { const update = this.getUpdate(); const updatedName = update.name; if (!updatedName) { return next(); } const currentDoc = await this.model('Company').findOne(this.getQuery()); if (!currentDoc) { return next(new Error('Company not found')); } await validateUniqueCompanyName.call(this, currentDoc, updatedName); next(); } catch (err) { console.error('Error in pre-updateOne validation:', err.message); next(err); } });
Key Improvements
- Single Source of Truth: Your validation logic lives in one place, so you only need to maintain it once.
- Fixes
thisBinding: No more issues withthisnot pointing to the document inside callbacks. - Handles Edge Cases: Skips validation when the name isn't changed, and excludes the current document from the duplicate check.
- Cleaner Error Handling:
async/awaiteliminates callback hell and makes error logging more straightforward.
Test Scenarios to Verify
Make sure to test these cases to ensure everything works as expected:
- Creating a new company with an existing name throws the "Company Name exists!" error.
- Editing a company to use another existing company's name throws the error.
- Editing a company without changing its name passes validation.
- Editing a company to use a unique name passes validation.
内容的提问来源于stack exchange,提问作者Farhan

