jjzjj

node.js - $addToSet 基于对象键存在

coder 2023-10-30 原文

我有数组 'pets': [{'fido': ['abc']} 这是一个嵌入文档。当我将宠物添加到数组中时,如何检查该宠物是否已经存在?例如,如果我再次添加 fido ......我如何检查是否只有 fido 存在而不添加它?我希望我可以使用 $addToSet 但我只想检查集合的一部分(宠物名称)。

User.prototype.updatePetArray = function(userId, petName) {
  userId = { _id: ObjectId(userId) };
  return this.collection.findOneAndUpdate(userId,
    { $addToSet: { pets: { [petName]: [] } } },
    { returnOriginal: false,
      maxTimeMS: QUERY_TIME });

添加两次fido的结果:

{u'lastErrorObject': {u'updatedExisting': True, u'n': 1}, u'ok': 1, u'value': {u'username': u'bob123', u'_id': u'56d5fc8381c9c28b3056f794', u'location': u'AT', u'pets': [{u'fido': []}]}}

{u'lastErrorObject': {u'updatedExisting': True, u'n': 1}, u'ok': 1, u'value': {u'username': u'bob123', u'_id': u'56d5fc8381c9c28b3056f794', u'location': u'AT', u'pets': [{u'fido': [u'abc']}, {u'fido': []}]}}

最佳答案

如果 “pets” 数组的每个成员中总是有“可变”内容(即 petName 作为键),则 $addToSet不适合你。至少不是在您要应用它的数组级别。

相反,您基本上需要一个 $exists测试数组中包含的文档的“键”,然后将 $addToSetpositional $ 匹配的键的“包含”数组运算符,,其中“键”不匹配然后 $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() 的 Promise 解析,然后通过 .findOne() 完成后。尽管在修改之后通过 .findOne() 执行此操作并不是真正的“原子”,并且可能在进行另一次类似修改“之后”返回文档,而不仅仅是在该特定状态下改变。


N.B 还假设除了 "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"]}]}

随意更改为不同的值以查看如何应用不同的“集”。

关于node.js - $addToSet 基于对象键存在,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35762192/

有关node.js - $addToSet 基于对象键存在的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  4. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  5. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  6. ruby-on-rails - 未在 Ruby 中初始化的对象 - 2

    我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调

  7. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

  8. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

  9. ruby - 更改 ActiveRecord 中对象的类 - 2

    假设我有一个FireNinja我的数据库中的对象,使用单表继承存储。后来才知道他真的是WaterNinja.将他更改为不同的子类的最干净的方法是什么?更好的是,我很想创建一个新的WaterNinja对象并替换旧的FireNinja在数据库中,保留ID。编辑我知道如何创建新的WaterNinja来self现有FireNinja的对象,我也知道我可以删除旧的并保存新的。我想做的是改变现有项目的类别。我是通过创建一个新对象并执行一些ActiveRecord魔法来替换行,还是通过对对象本身做一些疯狂的事情,或者甚至通过删除它并使用相同的ID重新插入来做到这一点,这是问题的一部分。

  10. ruby-on-rails - ActiveRecord 对象相等 - 2

    根据ActiveRecord::Base的文档:==(comparison_object)Returnstrueifcomparison_objectisthesameexactobject,orcomparison_objectisofthesametypeandselfhasanIDanditisequaltocomparison_object.id.Notethatnewrecordsaredifferentfromanyotherrecordbydefinition,unlesstheotherrecordisthereceiveritself.Besides,ifyoufet

随机推荐