MongoDB更改数组的顺序

时间:2018-11-21 23:55:37

标签: javascript mongodb

我对此感到困惑。我的收藏夹中有一个图像数组,用户可以在客户端重新排列图像的顺序,我正在尝试将新顺序保存到数据库中。 imagesOrder数组是新顺序中的新图像,它仅具有url,因此我想将该url与数据库中的url进行匹配。我不确定如何使索引成为变量,或者这是否可行:

这是我到目前为止所拥有的。我的代码编辑器显示[index]并出现错误,所以我知道这不是正确的格式,但不确定是什么:

imagesOrder.forEach((index, image) => {
  const imageUrl = image.url
  const index = index 
  Users.update({
    id
    }, {
      $set: {
        images[index]: imageUrl
      }
    })
  });

1 个答案:

答案 0 :(得分:2)

因此,实际上这并不是您执行此操作的方式。基本上,不需要为数组的每个索引位置实际向服务器发送更新请求。此外,update()方法是 asynchronous ,因此您永远不会在forEach()里面放东西,而它不尊重等待异步调用的完成。

通常,最实用的解决方案是仅在一个请求中$set整个数组内容。还可以将imagesOrder模拟为实用的东西,因为forEach()甚至实际上具有.forEach((<element>,><index>) => ...的签名,这似乎与问题代码所期望的不同。

 var imagesOrder = [
  { index: 0, url: '/one' }, { index: 1, url: '/two' }, { index: 2, url: '/three' }
 ];

 let response = await Users.updateOne(
   { id },
   { "$set": { "images": imagesOrder.map(({ url }) => url) } }
 );

 // { "$set": { "images": ["/one","/two","/three"] } }

类似于forEach()map()进行相同的数组迭代,但不同之处在于它实际上返回由处理函数生成的数组。实际上,这就是您想要的,因为这里需要的只是从每个对象中提取url属性的值。

请注意,index属性实际上已经井然有序,在这里确实很多余,但是我只是根据您的问题估算出听起来像什么。由于“数组”实际上保持了自己的顺序,因此这种属性“应该”是多余的,建议您的源数组数据实际上符合此要求。

但是,如果您设法以实际上不规则的方式记录了这些index值,那么最好的解决方案是添加一个sort()

 var imagesOrder = [
  { index: 2, url: '/three' }, { index: 0, url: '/one' }, { index: 1, url: '/two' }
 ];

 let response = await Users.updateOne(
   { id },
   { "$set": {
     "images": imagesOrder.sort((a,b) => a.index - b.index)
       .map(({ url }) => url)
   }}
 );

 // { "$set": { "images": ["/one","/two","/three"] } }

对于您“尝试”的内容,以任何方式实际尝试在给定位置上更新每个元素并没有真正使您受益。但是,如果您真的想看到它完成了,那么实际上,您实际上只是建立了一个更新请求:

 var imagesOrder = [
  { index: 2, url: '/three' }, { index: 0, url: '/one' }, { index: 1, url: '/two' }
 ];

 var update = { $set: {} };

 for ( let { url, index } of imagesOrder.sort((a,b) => a.index - b.index) ) {
    update.$set['images.'+index] = url;
 }

 /*
  * Creates:
  *
  * { "$set": {
  *   "images.0": "/one",
  *   "images.1": "/two",
  *   "images.2": "/three"
  * }}
  */       

 let response = await Users.updateOne({ id }, update);

或者在index属性不存在或无关紧要的情况下,因为已经对数组进行了排序:

 var imagesOrder = [
  { index: 2, url: '/three' }, { index: 0, url: '/one' }, { index: 1, url: '/two' }
 ];

 for ( let [index, { url }] of Object.entries(imagesOrder) ) {
    update.$set['images.'+index] = url;
 }

 /*
  * Creates:
  *
  * { "$set": {
  *   "images.0": "/one",
  *   "images.1": "/two",
  *   "images.2": "/three"
  * }}
  */       

 let response = await Users.updateOne({ id }, update);

所以几乎都是同一件事。注意,符号的常见形式实际上是键的“字符串”,其中包括数字形式的索引位置。在MongoDB查询语言的核心文档中的Dot Notation中对此进行了描述。

这里的一个主要区别是,如果新数组包含的条目比要修改的文档中存储的实际数组更多,则第二种使用“点符号”表示索引位置的格式将失败,因为它不能“设置”不存在的索引位置。

由于这个原因,即使如原始示例所示,“替换”数组还有其他陷阱,但比尝试通过存储的文档中的位置索引进行更新要安全得多。


请注意,这足以使您至少从正确的方向入手。与多个用户一起使用可能一次更新数据的工作在检查和合并更改的更新语句方面会变得非常复杂。

在大多数情况下,至少在一段时间内,简单的“替换”将绰绰有余。当然,这里的主要教训应该是不要在完全没有必要的地方循环“异步”方法。大多数时候,您真正想要“循环”的是语句的构造,当然,如果根本不需要任何循环,并且在大多数情况下,实际上并不需要。


附录

如果您或任何人想到实际上存储其中存储有index位置值的对象数组,这可能会变得稍微复杂一些,但它也可以用作示例考虑到它不依赖于数组的索引位置而是使用匹配条件,因此如何发出不“替换”数组并且实际上是安全的更新语句。

这可以通过MongoDB 3.6中引入的positional filtered $[<identifier>]语法实现。这允许条件指定要更新的元素(即通过匹配url),而不是直接在语句中包括索引位置。 更安全,因为如果找不到匹配的元素,则该语法允许根本不尝试更改任何内容。

作为演示,还显示了基于更新的index$sort元素的方法。注意,尽管在此语句中我们实际上并未向数组添加任何内容,但这实际上使用了$push修饰符。只是重新排列元素。但这实际上是您自动执行此操作的方式:

const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/longorder';
const opts = { useNewUrlParser: true };

// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// schema defs

const imageSchema = new Schema({
  index: Number,
  url: String
})

const userSchema = new Schema({
  images: [imageSchema]
});

const User = mongoose.model('User', userSchema);

// log helper

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri, opts);

    // clean models
    await Promise.all(
      Object.entries(conn.models).map(([k,m]) => m.deleteMany())
    );

    // Create data

    let _id = new ObjectId();

    let user = await User.findOneAndUpdate(
      { _id },
      {
        '$push': {
          'images': {
            '$each': [
              { index: 2, url: '/one' },
              { index: 0, url: '/three' },
              { index: 1, url: '/two' }
            ],
            '$sort': { 'index': 1 }
          }
        }
      },
      { 'new': true, 'upsert': true }
    );

    log(user);

    // Change order request
    let orderImages = [
      { index: 2, url: '/three' },
      { index: 0, url: '/one' },
      { index: 1, url: '/two' }
    ];

    let $set = { };
    let arrayFilters = [];

    for ( { index, url } of orderImages ) {
      let key = url.replace(/^\//,'');
      arrayFilters.push({ [`${key}.url`]: url });
      $set[`images.$[${key}].index`] = index;
    }

    let ops = [
      // Update the index value of each matching item
      { 'updateOne': {
        'filter': { _id },
        'update': { $set },
        arrayFilters
      }},
      // Re-sort the array by index value
      { 'updateOne': {
        'filter': { _id },
        'update': {
          '$push': {
            'images': { '$each': [], '$sort': { 'index': 1 } }
          }
        }
      }}
    ];

    log(ops);

    let response = await User.bulkWrite(ops);
    log(response);

    let newuser = await User.findOne({ _id });
    log(newuser);


  } catch(e) {
    console.error(e)
  } finally {
    mongoose.disconnect()
  }

})()

输出显示原始文档状态,更新和实际更改:

Mongoose: users.deleteMany({}, {})
Mongoose: users.findOneAndUpdate({ _id: ObjectId("5bf6116621293f2ab3dec3d3") }, { '$setOnInsert': { __v: 0 }, '$push': { images: { '$each': [ { _id: ObjectId("5bf6116621293f2ab3dec3d6"), index: 2, url: '/one' }, { _id: ObjectId("5bf6116621293f2ab3dec3d5"), index: 0, url: '/three' }, { _id: ObjectId("5bf6116621293f2ab3dec3d4"), index: 1, url: '/two' } ], '$sort': { index: 1 } } } }, { upsert: true, remove: false, projection: {}, returnOriginal: false })
{
  "_id": "5bf6116621293f2ab3dec3d3",
  "__v": 0,
  "images": [
    {
      "_id": "5bf6116621293f2ab3dec3d5",
      "index": 0,
      "url": "/three"
    },
    {
      "_id": "5bf6116621293f2ab3dec3d4",
      "index": 1,
      "url": "/two"
    },
    {
      "_id": "5bf6116621293f2ab3dec3d6",
      "index": 2,
      "url": "/one"
    }
  ]
}
[
  {
    "updateOne": {
      "filter": {
        "_id": "5bf6116621293f2ab3dec3d3"
      },
      "update": {
        "$set": {
          "images.$[three].index": 2,
          "images.$[one].index": 0,
          "images.$[two].index": 1
        }
      },
      "arrayFilters": [
        {
          "three.url": "/three"
        },
        {
          "one.url": "/one"
        },
        {
          "two.url": "/two"
        }
      ]
    }
  },
  {
    "updateOne": {
      "filter": {
        "_id": "5bf6116621293f2ab3dec3d3"
      },
      "update": {
        "$push": {
          "images": {
            "$each": [],
            "$sort": {
              "index": 1
            }
          }
        }
      }
    }
  }
]
Mongoose: users.bulkWrite([ { updateOne: { filter: { _id: 5bf6116621293f2ab3dec3d3 }, update: { '$set': { 'images.$[three].index': 2, 'images.$[one].index': 0, 'images.$[two].index': 1 } }, arrayFilters: [ { 'three.url': '/three' }, { 'one.url': '/one' }, { 'two.url': '/two' } ] } }, { updateOne: { filter: { _id: 5bf6116621293f2ab3dec3d3 }, update: { '$push': { images: { '$each': [], '$sort': { index: 1 } } } } } } ], {})
{
  "ok": 1,
  "writeErrors": [],
  "writeConcernErrors": [],
  "insertedIds": [],
  "nInserted": 0,
  "nUpserted": 0,
  "nMatched": 2,
  "nModified": 2,
  "nRemoved": 0,
  "upserted": [],
  "lastOp": {
    "ts": "6626503031506599940",
    "t": 139
  }
}
Mongoose: users.findOne({ _id: ObjectId("5bf6116621293f2ab3dec3d3") }, { projection: {} })
{
  "_id": "5bf6116621293f2ab3dec3d3",
  "__v": 0,
  "images": [
    {
      "_id": "5bf6116621293f2ab3dec3d6",
      "index": 0,
      "url": "/one"
    },
    {
      "_id": "5bf6116621293f2ab3dec3d4",
      "index": 1,
      "url": "/two"
    },
    {
      "_id": "5bf6116621293f2ab3dec3d5",
      "index": 2,
      "url": "/three"
    }
  ]
}