如前所述,$addToSet 不能以这种方式工作,因为数组或“集合”中的元素旨在真正代表一个“集合”,其中每个元素都是完全唯一的。此外,.update() 等操作方法不考虑 mongoose 架构默认或验证规则。
但是,.update() 等操作比“查找”文档,然后操作和使用 .save() 来更改客户端代码要有效得多。它们还避免了其他进程或事件操作可能在检索文档后对其进行修改的并发问题。
要执行您想要的操作,需要向服务器发出“多个”更新语句。这是一种“回退”逻辑情况,当一个操作未更新文档时,您回退到下一个操作:
models/category.js:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var category = new Schema({
Category_Name: { type: String, required: true},
Category_Type: { type: String},
Sub_Categories: [{Sub_Category_Name: String, UpdatedOn: { type:Date, default:Date.now} }],
CreatedDate: { type:Date, default: Date.now},
UpdatedOn: {type: Date, default: Date.now}
});
exports.Category = mongoose.model( "Category", category );
在您的代码中:
var Category = require('models/category').Category;
exports.addCategory = function(req,res) {
var category_name = req.body.category_name;
var parent_category_id = req.body.parent_categoryId;
Category.update(
{
"_id": parent_category_id,
"Sub_Categories.Sub_Category_Name": category_name
},
{
"$set": { "Sub_Categories.$.UpdatedOn": new Date() }
},
function(err,numAffected) {
if (err) throw error; // or handle
if ( numAffected == 0 )
Category.update(
{
"_id": parent_category_id,
"Sub_Categories.Sub_Category_Name": { "$ne": category_name }
},
{
"$push": {
"Sub_Categories": {
"Sub_Category_Name": category_name,
"UpdatedOn": new Date()
}
}
},
function(err,numAffected) {
if (err) throw err; // or handle
if ( numAffected == 0 )
Category.update(
{
"_id": parent_category_id
},
{
"$push": {
"Sub_Categories": {
"Sub_Category_Name": category_name,
"UpdatedOn": new Date()
}
}
},
{ "$upsert": true },
function(err,numAffected) {
if (err) throw err;
}
);
});
);
}
);
};
基本上尝试了三种可能的操作:
尝试匹配存在类别名称的文档并更改匹配数组元素的“UpdatedOn”值。
如果没有更新。查找与 parentId 匹配但类别名称不存在于数组中的文档并推送新元素。
如果没有更新。执行一个尝试匹配 parentId 的操作,并将 upsert 设置为 true 的数组元素推送。由于之前的两次更新都失败了,这基本上是一个插入。
您可以通过使用 async.waterfall 之类的方法来清除它,以传递 numAffected 值并避免缩进蔓延,或者根据我个人的偏好,不费心检查受影响的值并一次传递所有语句通过Bulk Operations API 到服务器。
后者可以像这样从猫鼬模型中访问:
var ObjectId = mongoose.mongo.ObjectID,
Category = require('models/category').Category;
exports.addCategory = function(req,res) {
var category_name = req.body.category_name;
var parent_category_id = req.body.parent_categoryId;
var bulk = Category.collection.initializeOrderBulkOp();
// Reversed insert
bulk.find({ "_id": { "$ne": new ObjectId( parent_category_id ) })
.upsert().updateOne({
"$setOnInsert": { "_id": new ObjectId( parent_category_id ) },
"$push": {
"Sub_Category_Name": category_name,
"UpdatedOn": new Date()
}
});
// In place
bulk.find({
"_id": new ObjectId( parent_category_id ),
"Sub_Categories.Sub_Category_Name": category_name
}).updateOne({
"$set": { "Sub_Categories.$.UpdatedOn": new Date() }
});
// Push where not matched
bulk.find({
"_id": new ObjectId( parent_category_id ),
"Sub_Categories.Sub_Category_Name": { "$ne": category_name }
}).updateOne({
"$push": {
"Sub_Category_Name": category_name,
"UpdatedOn": new Date()
}
});
// Send to server
bulk.execute(function(err,response) {
if (err) throw err; // or handle
console.log( JSON.stringify( response, undefined, 4 ) );
});
};
注意“upsert”首先出现的相反逻辑,但如果成功,则只有“second”语句将适用,但实际上在 Bulk API 下,这不会影响文档。您将获得一个WriteResult 对象,其基本信息与此类似(简略形式):
{ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }
或者在“upsert”上:
{
"nMatched" : 1,
"nUpserted" : 1,
"nModified" : 0,
"_id" : ObjectId("54af8fe7628bee196ce97ce0")
}
还要注意需要包含来自基本 mongo 驱动程序的 ObjectId 函数,因为这是来自基本驱动程序的“原始”方法,它不像 mongoose 方法那样基于架构“自动转换”。
另外要非常小心,因为它是一个基本驱动方法并且不共享猫鼬逻辑,所以如果没有建立到数据库的连接,那么调用.collection访问器将不会返回Collection对象和随后的方法调用失败。 Mongoose 本身对数据库连接进行“惰性”实例化,并且方法调用被“排队”,直到连接可用。基本驱动程序方法并非如此。
所以可以做到,只是您需要自己处理此类数组处理的逻辑,因为没有本地运算符可以做到这一点。但是,如果您采取适当的措施,它仍然非常简单且非常有效。