更新文档和/或添加子文档

编程入门 行业动态 更新时间:2024-10-22 10:37:28
本文介绍了更新文档和/或添加子文档的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我一直在努力尝试MongoDB,Mongoose和JavaScript的异步特性,以及如何最好地对集合进行多次更新.

I've been wrestling with the asynchronous nature of MongoDB, Mongoose and JavaScript and how to best make multiple updates to a collection.

我有一个包含客户和联系数据的Excel工作表.有些客户端具有多个联系人,每行一个,并且客户端数据是相同的(因此,客户端名称可以用作唯一键-实际上,在使用unique: true定义的架构中).

I have an Excel sheet of client and contact data. There are some clients with multiple contacts, one per line, and the client data is the same (so the client name can be used as a unique key - in fact in the schema it's defined with unique: true).

我要实现的逻辑是:

  • 以clientName作为关键字在Client集合中搜索客户端
  • 如果找不到匹配的clientName,则为该客户端创建一个新文档(不是upsert,如果客户端文档已在数据库中,我不想更改任何内容)
  • 使用firstName和lastName作为键,检查联系人是否已存在于客户文档中的联系人数组中
  • 如果未找到该联系人,则$push该联系人进入阵列
  • Search the Client collection for the client with clientName as the key
  • If a matching clientName isn't found then create a new document for that client (not an upsert, I don't want to change anything if the client document is already in the database)
  • Check to see if the contact is already present in the array of contacts within the client document using firstName and lastName as the keys
  • If the contact isn't found then $push that contact onto the array
  • 当然,我们很容易遇到这样的情况:客户不存在(因此创建),然后,工作表的下一行立即是同一客户的另一个联系人,所以我想找到现有的(刚刚创建的)客户端,并将第二个新联系人$push插入数组.

    Of course, we could easily have a situation where the client doesn't exists (and so is created) and then immediately, the very next row of the sheet, is another contact for the same client so then I'd want to find that existing (just created) client and $push that 2nd new contact into the array.

    我已经尝试过了,但是没有用:

    I've tried this but it's not working:

    Client.findOneAndUpdate( {clientName: obj.client.clientname}, {$set: obj.client, $push: {contacts: obj.contact}}, {upsert: true, new: true}, function(err, client){ console.log(client) } )

    我也很好地看过其他问题,例如:

    and I've had a good look at other questions, e.g.:

    • 自动地创建带有子文档的mongodb文档?
    • stackoverflow/questions/28026197/upserting-complex通过猫鼬通过快递
    • create mongodb document with subdocuments atomically?
    • stackoverflow/questions/28026197/upserting-complex-collections-through-mongoose-via-express

    但无法获得解决方案...我得出的结论是,也许我必须使用一些应用程序逻辑来进行查找,然后在我的代码中做出决定,然后编写,而不是使用单个Mongoose/Mongo声明,但随后的异步问题使他们头昏脑沉.

    but can't get a solution... I'm coming to the conclusion that maybe I have to use some app logic to do the find, then decisions in my code, then writes, rather than use a single Mongoose/Mongo statement, but then the issues of asynchronicity rear their ugly head.

    有什么建议吗?

    推荐答案

    处理此问题的方法不是简单的方法,因为将"upserts"与在"arrays"中添加项混合在一起很容易导致不良结果.这还取决于您是否希望逻辑设置其他字段,例如计数器"来指示数组中有多少个联系人,而您只想在分别添加或删除项时增加/减少.

    The approach to handling this is not a simple one, as mixing "upserts" with adding items to "arrays" can easily lead to undesired results. It also depends on if you want logic to set other fields such as a "counter" indicating how many contacts there are within an array, which you only want to increment/decrement as items are added or removed respectively.

    但是,在最简单的情况下,如果联系人"仅包含单个值,例如链接到另一个集合的ObjectId,则 $addToSet 修改器就可以很好地工作:

    In the most simple case however, if the "contacts" only contained a singular value such as an ObjectId linking to another collection, then the $addToSet modifier works well, as long as there no "counters" involved:

    Client.findOneAndUpdate( { "clientName": clientName }, { "$addToSet": { "contacts": contact } }, { "upsert": true, "new": true }, function(err,client) { // handle here } );

    这很好,因为您只测试看看文档是否与"clientName"匹配,如果没有,则添加它.无论是否存在匹配项,$addToSet运算符都会处理唯一的奇异"值,即任何真正唯一的对象".

    And that is all fine as you are only testing to see if a doucment matches on the "clientName", if not upsert it. Whether there is a match or not, the $addToSet operator will take care of unique "singular" values, being any "object" that is truly unique.

    遇到类似问题的地方就会出现困难:

    The difficulties come in where you have something like:

    { "firstName": "John", "lastName": "Smith", "age": 37 }

    已经在通讯录数组中,然后您想要执行以下操作:

    Already in the contacts array, and then you want to do something like this:

    { "firstName": "John", "lastName": "Smith", "age": 38 }

    您的实际意图是,这是与约翰·史密斯相同"的,只是年龄"没有不同.理想情况下,您只是想更新"该数组入口端创建一个新数组或一个新文档.

    Where your actual intention is that this is the "same" John Smith, and it's just that the "age" is not different. Ideally you want to just "update" that array entry end neiter create a new array or a new document.

    要在.findOneAndUpdate()上进行此操作(希望将更新后的文档返回),可能很困难.因此,如果您真的不希望修改后的文档得到响应,请批量操作MongoDB的API 和核心驱动程序在这里最有帮助.

    Working this with .findOneAndUpdate() where you want the updated document to return can be difficult. So if you don't really want the modified document in response, then the Bulk Operations API of MongoDB and the core driver are of most help here.

    考虑以下语句:

    var bulk = Client.collection.initializeOrderedBulkOP(); // First try the upsert and set the array bulk.find({ "clientName": clientName }).upsert().updateOne({ "$setOnInsert": { // other valid client info in here "contacts": [contact] } }); // Try to set the array where it exists bulk.find({ "clientName": clientName, "contacts": { "$elemMatch": { "firstName": contact.firstName, "lastName": contact.lastName } } }).updateOne({ "$set": { "contacts.$": contact } }); // Try to "push" the array where it does not exist bulk.find({ "clientName": clientName, "contacts": { "$not": { "$elemMatch": { "firstName": contact.firstName, "lastName": contact.lastName }} } }).updateOne({ "$push": { "contacts": contact } }); bulk.execute(function(err,response) { // handle in here });

    这很好,因为此处的Bulk Operations意味着此处的所有语句都立即发送到服务器,并且只有一个响应.还要注意,这里的逻辑意味着最多只有两个操作实际上会修改任何内容.

    This is nice since the Bulk Operations here mean that all statements here are sent to the server at once and there is only one response. Also note here that the logic means here that at most only two operations will actually modify anything.

    首先, $setOnInsert > 修饰符可确保在文档只是匹配项时,不会进行任何更改.由于此处唯一的修改是在该块内,因此仅影响发生"upsert"的文档.

    In the first instance, the $setOnInsert modifier makes sure that nothing is changed when the document is just a match. As the only modifications here are within that block, this only affects a document where an "upsert" occurs.

    还要注意接下来的两个语句,您不要尝试再次"upsert".这认为第一条语句可能必须成功,否则就没有关系.

    Also note on the next two statements you do not try to "upsert" again. This considers that the first statement was possibly successful where it had to be, or otherwise did not matter.

    之所以没有"upsert"的另一个原因是,测试数组中元素的存在所需的条件将导致在不满足条件时导致新文档的"upsert".这是不希望的,因此没有"upsert".

    The other reason for no "upsert" there is because the condtions needed to test the presence of the element in the array would lead to the "upsert" of a new document when they were not met. That is not desired, therefore no "upsert".

    他们实际上所做的是分别检查数组元素是否存在,并更新现有元素或创建一个新元素.因此,总的来说,所有操作都意味着您在发生更新问题的情况下要么一次"修改,要么最多两次"修改.可能的两次"产生的开销很小,也没有真正的问题.

    What they do in fact is respectively check whether the array element is present or not, and either update the existing element or create a new one. Therefore in total, all operations mean you either modify "once" or at most "twice" in the case where an upsert occurred. The possible "twice" creates very little overhead and no real problem.

    在第三条语句中, $not > 运算符会反转 $elemMatch 确定不存在具有查询条件的数组元素.

    Also in the third statement the $not operator reverses the logic of the $elemMatch to determine that no array element with the query condition exists.

    用.findOneAndUpdate()进行翻译变得有点麻烦了.现在不仅重要的是成功",还决定了最终内容的返回方式.

    Translating this with .findOneAndUpdate() becomes a bit more of an issue. Not only is it the "success" that matters now, it also determines how the eventual content is returned.

    因此,最好的办法是按系列"运行事件,然后对结果进行一些魔术处理,以返回最终的已更新"表单.

    So the best idea here is to run the events in "series", and then work a little magic with the result in order to return the end "updated" form.

    我们将在此处使用的帮助与 async.waterfall 和 lodash 库:

    The help we will use here is both with async.waterfall and the lodash library:

    var _ = require('lodash'); // letting you know where _ is coming from async.waterfall( [ function(callback) { Client.findOneAndUpdate( { "clientName": clientName }, { "$setOnInsert": { // other valid client info in here "contacts": [contact] } }, { "upsert": true, "new": true }, callback ); }, function(client,callback) { Client.findOneAndUpdate( { "clientName": clientName, "contacts": { "$elemMatch": { "firstName": contact.firstName, "lastName": contact.lastName } } }, { "$set": { "contacts.$": contact } }, { "new": true }, function(err,newClient) { client = client || {}; newClient = newClient || {}; client = _.merge(client,newClient); callback(err,client); } ); }, function(client,callback) { Client.findOneAndUpdate( { "clientName": clientName, "contacts": { "$not": { "$elemMatch": { "firstName": contact.firstName, "lastName": contact.lastName }} } }, { "$push": { "contacts": contact } }, { "new": true }, function(err,newClient) { newClient = newClient || {}; client = _.merge(client,newClient); callback(err,client); } ); } ], function(err,client) { if (err) throw err; console.log(client); } );

    遵循与以前相同的逻辑,因为实际上只有两个或一个语句会执行任何操作,而返回的新"文档可能会成为null.这里的瀑布"将结果从每个阶段传递到下一个阶段,包括最后一个错误也将立即分支到的末端.

    That follows the same logic as before in that only two or one of those statements is actually going to do anything with the possibility that the "new" document returned is going to be null. The "waterfall" here passes a result from each stage onto the next, including the end where also any error will immediately branch to.

    在这种情况下,null将交换为空对象{},而 _.merge() 方法将在以后的每个阶段将两个对象组合为一个对象.不管先前的操作实际上做了什么,这都会为您提供最终结果,即修改后的对象.

    In this case the null would be swapped for an empty object {} and the _.merge() method will combine the two objects into one, at each later stage. This gives you the final result which is the modified object, no matter which preceeding operations actually did anything.

    当然,$pull需要进行不同的操作,而且您的问题本身具有输入数据作为对象形式.但是,这些实际上本身就是答案.

    Of course, there would be a differnt manipulation required for $pull, and also your question has input data as an object form in itself. But those are actually answers in themselves.

    这至少应该使您开始了解如何处理更新模式.

    This should at least get you started on how to approach your update pattern.

    更多推荐

    更新文档和/或添加子文档

    本文发布于:2023-11-28 10:09:39,感谢您对本站的认可!
    本文链接:https://www.elefans.com/category/jswz/34/1641975.html
    版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
    本文标签:文档

    发布评论

    评论列表 (有 0 条评论)
    草根站长

    >www.elefans.com

    编程频道|电子爱好者 - 技术资讯及电子产品介绍!