如果有总是将成为"pets"
阵列(即petName作为密钥)的每个成员中“可变的”内容然后$addToSet
是不适合你。至少不是在您希望应用它的阵列级别。
相反,你基本上需要对文档的“钥匙”的$exists
测试被包含在阵列中,那么无论$addToSet
为“载”与positional $
运营商匹配键的阵列,或其中“键“直接与$push
不匹配的”宠物“阵列,新的内部content
直接作为唯一的阵列成员。
因此,如果您可以忍受未返回修改过的文档,那么“批量”操作适合您。在现代车手与bulkWrite()
:
User.prototype.updatePetArray = function(userId, petName, content) {
var filter1 = { "_id": ObjectId(userId) },
filter2 = { "_id": ObjectId(userId) },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
// Return the promise that yields the BulkWriteResult of both calls
return this.collection.bulkWrite([
{ "updateOne": {
"filter": filter1,
"update": update1
}},
{ "updateOne": {
"filter": filter2,
"update": update2
}}
]);
};
如果必须退回修改的文件,那么你将需要解决每个调用和返回实际匹配的东西之一:
User.prototype.updatePetArray = function(userId, petName, content) {
var filter1 = { "_id": ObjectId(userId) },
filter2 = { "_id": ObjectId(userId) },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
// Return the promise that returns the result that matched and modified
return new Promise(function(resolve,reject) {
var operations = [
this.collection.findOneAndUpdate(filter1,update1,{ "returnOriginal": false}),
this.collection.findOneAndUpdate(filter2,update2,{ "returnOriginal": false})
];
// Promise.all runs both, and discard the null document
Promise.all(operations).then(function(result) {
resolve(result.filter(function(el) { return el.value != null })[0].value);
},reject);
});
};
在这两种情况下,这需要“两次”更新尝试,其中只有“一个”实际上会成功并修改文档,因为只有其中一个$exists
测试成立。
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets.fido": { "$exists": true }
},
{ "$addToSet": { "pets.$.fido": "ccc" } }
而第二个更新为:
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets.fido": { "$exists": false }
},
{ "$push": { "pets": { "fido": ["ccc"] } } }
的考虑变量下
所以作为第一种情况下,“查询”和“更新”的一个例子插值后解决:
userId = "56d7b759e955e2812c6c8c1b",
petName = "fido",
content = "ccc";
就我个人而言,我不会像这样命名键,而是将结构更改为:
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets": [{ "name": "fido", "data": ["abc"] }]
}
这使得更新语句更容易,而且不需要对键名进行可变内插。例如:
{
"_id": ObjectId(userId),
"pets.name": petName
},
{ "$addToSet": { "pets.$.data": content } }
和:
{
"_id": ObjectId(userId),
"pets.name": { "$ne": petName }
},
{ "$push": { "pets": { "name": petName, "data": [content] } } }
这感觉了一大堆清洁,可以实际使用的“指数”进行匹配,这当然$exists
根本无法。
如果使用.findOneAndUpdate()
,当然会有更多的开销,因为这对于需要等待响应的服务器来说是最后的“两次”实际调用,而Bulk方法只是“一个”。
但是如果你需要返回的文档(选项反正在驱动程序的默认),那么无论做或类似的等待无极从.bulkWrite()
解决,然后完成后通过.findOne()
提取文档。虽然修改后通过.findOne()
修改后不会真的是“原子的”,并且可能会在“之后”返回文档进行另一个类似的修改,而不仅仅是该特定更改的状态。
NB还假设除了子文档的键在"pets"
为“设置”您的其他意图用于阵列包含被添加到“设置”,以及经由提供给函数的附加content
。如果您只是想覆盖一个值,那么只需应用$set
而不是$addToSet
,并将其类似地换成数组。
但是,前者就是你所问的,这听起来很合理。
顺便说一句。请清理被可怕的设置代码在这个例子中的查询和更新对象实际代码:)
作为一个自包含上市证明:
var async = require('async'),
mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient;
MongoClient.connect('mongodb://localhost/test',function(err,db) {
var coll = db.collection('pettest');
var petName = "fido",
content = "bbb";
var filter1 = { "_id": 1 },
filter2 = { "_id": 1 },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
console.log(JSON.stringify(update1,undefined,2));
console.log(JSON.stringify(update2,undefined,2));
function CleanInsert(callback) {
async.series(
[
// Clean data
function(callback) {
coll.deleteMany({},callback);
},
// Insert sample
function(callback) {
coll.insert({ "_id": 1, "pets": [{ "fido": ["abc"] }] },callback);
}
],
callback
);
}
async.series(
[
CleanInsert,
// Modify Bulk
function(callback) {
coll.bulkWrite([
{ "updateOne": {
"filter": filter1,
"update": update1
}},
{ "updateOne": {
"filter": filter2,
"update": update2
}}
]).then(function(res) {
console.log(JSON.stringify(res,undefined,2));
coll.findOne({ "_id": 1 }).then(function(res) {
console.log(JSON.stringify(res,undefined,2));
callback();
});
},callback);
},
CleanInsert,
// Modify Promise all
function(callback) {
var operations = [
coll.findOneAndUpdate(filter1,update1,{ "returnOriginal": false }),
coll.findOneAndUpdate(filter2,update2,{ "returnOriginal": false })
];
Promise.all(operations).then(function(res) {
//console.log(JSON.stringify(res,undefined,2));
console.log(
JSON.stringify(
res.filter(function(el) { return el.value != null })[0].value
)
);
callback();
},callback);
}
],
function(err) {
if (err) throw err;
db.close();
}
);
});
和输出:
{
"$addToSet": {
"pets.$.fido": "bbb"
}
}
{
"$push": {
"pets": {
"fido": [
"bbb"
]
}
}
}
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 1,
"nModified": 1,
"nRemoved": 0,
"upserted": []
}
{
"_id": 1,
"pets": [
{
"fido": [
"abc",
"bbb"
]
}
]
}
{"_id":1,"pets":[{"fido":["abc","bbb"]}]}
随意更改为不同的值,以了解如何应用不同的“集合”。
谢谢!我最初有''宠物':[{“名字”:“fido”,“data”:[“abc”]}]'并且认为它不是很好。我会改变这一点......谢谢你确认这一点。 – dman
如果可能的话,我想避免'pets'数组上的额外索引,以节省内存。由于'pets'数组是User doc的嵌入式文档,因此不会搜索索引的'_id:ObjectId()'就足够了吗?我仍然可以使用'“pets.fido”:{“$ exists”:false}'和'“pets.name”:{“$ ne”:petName}'没有额外的索引和相同的性能? – dman
@dman只要你在查询中包含'_id',它就不会成为问题,因为文档已经被该值所选中。如果它在结构上有所作为,并且指数收益来自何处,那么您希望对此来源的文档进行任何类型的分析。命名键在分析中确实不能很好地工作,这是避免它们的强有力的理由。至于你的内存问题,这个get一次又一次提出,但真正的差别是微不足道的,甚至在有线老虎(内部哈希键)下更是如此。 –