在数组中填充对象 [英] Populate Object In an Array

查看:83
本文介绍了在数组中填充对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在填充用户时遇到麻烦. 情况:

Having trouble Populating my user. The case:

var User = new mongoose.Schema({
name: {
  type: String,
  lowercase: true,
  unique: true
},
portfolio:[
  {
      name: String,
      formatType: { type: mongoose.Schema.Types.ObjectId, ref: 'FormatType' },
  }
]
});

这是我的猫鼬命令:

User.findById(req.payload.id)
    .populate({
        path:'portfolio',
        populate:{
            path: 'formatType',
            model: 'FormatType'
        }
    })
    .then(user => { ... 

因此,这里我们有一个模型-一个Obect内部-一个数组内部-一个实体内部.

So what we have here is a model - inside of an Obect - inside of an array - inside of an entity.

无法在线找到答案,非常感谢〜!

Couldn't find an Answer online, would be very thankful~!

推荐答案

您基本上在这里错过的是您要populate()的字段的路径"实际上是'portfolio.formatType',而不仅仅是'portfolio'输入.由于该错误和结构,您可能会有一些普遍的误解.

What you basically missed here is the "path" to the field you want to populate() is actually 'portfolio.formatType' and not just 'portfolio' as you have typed. Due to that mistake and the structure, you might have a few general misconceptions though.

基本更正仅需要正确的路径,并且您不需要model参数,因为该参数已在模式中隐含.

The basic correction merely needs the correct path, and you don't need the model argument since this is already implied in the schema:

User.findById(req.params.id).populate('portfolio.formatType');

但是,通常在数组中混合"嵌入"数据和引用"数据通常不是一个好主意,实际上,您应该嵌入所有内容,也可以简单地引用所有内容.如果您打算引用文档,则在文档中保留引用数组通常也有点反模式",因为您的理由不应是使文档超过16MB BSON限制.在您的数据永远无法达到该限制的情况下,通常最好完全嵌入".这确实是一个更广泛的讨论,但您应该注意一些事情.

It is however generally not a great idea to "mix" both "embedded" data and "referenced" data within arrays, and you should really be either embedding everything or simply referencing everything. It's also a little bit of an "anti-pattern" in general to keep an array of references in the document if your intention is referencing, since your reason should be not to cause the document to grow beyond the 16MB BSON limit. And where that limit would never be reached by your data it's generally better to "embed fully". That's really a wider discussion, but something you should be aware of.

这里的下一个一般要点是populate()本身有点旧帽子",而实际上并不是大多数新用户认为它是魔术"的东西.要明确populate() NOT JOIN ,并且它所做的就是向服务器执行另一个查询以返回相关"项,然后将该内容合并到从中返回的文档中.上一个查询.

The next general point here is populate() itself is somewhat "old hat", and really not the "magical" thing most new users perceive it to be. To be clear populate() is NOT A JOIN, and all it is doing is executing another query to the server in order to return the "related" items, then merge that content into the documents returned from the previous query.

如果您正在寻找"joins",那么实际上您可能想要像前面提到的那样"embedded".这实际上是处理关系"的"MongoDB方法",但是将所有相关"数据保留在一个文档中.数据位于单独集合中的联接"的另一种方式是通过现代版本中的$lookup 运算符.

If you are looking for "joins", then really you probably wanted "embedding" as mentioned earlier. This is really the "MongoDB Way" of dealing with "relations" but keeping all "related" data together in the one document. The other means of a "join" where data is in separate collections is via the $lookup operator in modern releases.

由于您的内容数组形式是混合"的,因此变得更加复杂,但通常可以表示为:

This gets a bit more complex due to your "mixed" content array form, but can generally be represented as:

// Aggregation pipeline don't "autocast" from schema
const { Types: { ObjectId } } = require("mongoose");

User.aggregate([
  { "$match": { _id: ObjectId(req.params.id)  } },
  { "$lookup": {
    "from": FormatType.collection.name,
    "localField": "portfolio.formatType",
    "foreignField": "_id",
    "as": "formats"
  }},
  { "$project": {
    "name": 1,
    "portfolio": {
      "$map": {
        "input": "$portfolio",
        "in": {
          "name": "$$this.name",
          "formatType": {
            "$arrayElemAt": [
              "$formats",
              { "$indexOfArray": [ "$formats._id", "$$this.formatType" ] }
            ]
          }
        }
      }
    }
  }}
]);

或使用更具表现力的形式 $lookup 从MongoDB 3.6开始:

Or with the more expressive form of $lookup since MongoDB 3.6:

User.aggregate([
  { "$match": { _id: ObjectId(req.params.id)  } },
  { "$lookup": {
    "from": FormatType.collection.name,
    "let": { "portfolio": "$portfolio" },
    "as": "portfolio",
    "pipeline": [
      { "$match": {
        "$expr": {
          "$in": [ "$_id", "$$portfolio.formatType" ]
        }
      }},
      { "$project": {
        "_id": {
          "$arrayElemAt": [
            "$$portfolio._id",
            { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
          ]
        },
        "name": {
          "$arrayElemAt": [
            "$$portfolio.name",
            { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
          ]
        },
        "formatType": "$$ROOT",
      }}
    ]
  }}
]);

这两种方法的工作方式略有不同,但是本质上都与以下概念一起工作:返回匹配的相关"条目,然后重新映射"到现有数组内容上,以便与"name"属性嵌入"合并在数组中.这实际上是主要的并发症,否则它是一种相当简单的检索方法.

The two approaches work slightly differently, but both essentially work with the concept of returning the matching "related" entries and then "re-mapping" onto the existing array content in order to merge with the "name" properties "embedded" inside the array. That is actually the main complication that otherwise is a fairly straightforward method of retrieval.

populate()在客户端"上实际执行但在服务器"上执行的几乎相同的过程.因此,比较使用 $indexOfArray 运算符来查找匹配的ObjectId值是,然后通过 $arrayElemAt 操作.

It's pretty much the same process as what populate() actually does on the "client" but executed on the "server". So the comparisons are using the $indexOfArray operator to find where the matching ObjectId values are and then return a property from the array at that matched "index" via the $arrayElemAt operation.

唯一的区别是,在MongoDB 3.6兼容版本中,我们在外国"内容 之前" 中进行替换",将合并结果返回到父母.在以前的版本中,我们返回整个匹配的外部数组,然后使用

The only difference is that in the MongoDB 3.6 compatible version, we do that "substitution" within the "foreign" content "before" the joined results are returned to the parent. In prior releases we return the whole matching foreign array and then "marry up" the two to form a singular "merged" array using $map.

虽然它们最初看起来可能比较复杂",但这里的最大优点是它们构成了 单个请求" 并带有 单个响应" ,而不像populate()那样发出和接收多个"请求.实际上,这实际上节省了网络流量的大量开销,并大大增加了响应时间.

Whilst these may initially look "more complex", the big advantage here is that these constitute a "single request" to the server with a "single response" and not issuing and receiving "multiple" requests as populate() does. This actually saves a lot of overhead in network traffic and greatly increases response time.

此外,这些是真正的联接",因此您可以做很多事,而多重查询"是无法实现的.例如,您可以在"join"上对结果进行排序",并且仅返回最前面的结果,因为使用populate()需要拉入所有父母",然后才能寻找要返回结果的孩子".子"join"上的过滤"条件也是如此.

In addition, these are "real joins" so there is a lot more you can do which cannot be achieved with "multiple queries". For instance you can "sort" results on the "join" and only return the top results, where as using populate() needs to pull in "all parents" before it can even look for which "children" to return in result. The same goes for "filtering" conditions on the child "join" as well.

有关在猫鼬中填充后的查询,对此有更多详细信息一般限制以及您实际上可以实际执行的操作,以在需要时自动化"这种复杂"聚合管道语句的生成.

There is some more detail on this on Querying after populate in Mongoose about the general limitations and what you actually can even practically do to "automate" the generation of such "complex" aggregation pipeline statements where needed.

进行这些连接"并大致理解引用的架构的另一个常见问题是,人们常常在关于何时何地存储引用以及它们如何工作方面会弄错概念.因此,以下清单展示了此类数据的存储和检索.

Another common problem with doing these "joins" and understanding referenced schema in general is that people often get the concepts wrong on where and when to store the references and how it all works. Therefore the following listings serve as demonstration of both the storage and retrieval of such data.

在较旧的NodeJS版本的本机Promises实现中:

In a native Promises implementation for older NodeJS releases:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/usertest';

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const formatTypeSchema = new Schema({
  name: String
});

const portfolioSchema = new Schema({
  name: String,
  formatType: { type: Schema.Types.ObjectId, ref: 'FormatType' }
});

const userSchema = new Schema({
  name: String,
  portfolio: [portfolioSchema]
});

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

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

(function() {

  mongoose.connect(uri).then(conn => {

    let db = conn.connections[0].db;

    return db.command({ buildInfo: 1 }).then(({ version }) => {
      version = parseFloat(version.match(new RegExp(/(?:(?!-).)*/))[0]);

      return Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()))
        .then(() => FormatType.insertMany(
          [ 'A', 'B', 'C' ].map(name => ({ name }))
        )
        .then(([A, B, C]) => User.insertMany(
          [
            {
              name: 'User 1',
              portfolio: [
                { name: 'Port A', formatType: A },
                { name: 'Port B', formatType: B }
              ]
            },
            {
              name: 'User 2',
              portfolio: [
                { name: 'Port C', formatType: C }
              ]
            }
          ]
        ))
        .then(() => User.find())
        .then(users => log({ users }))
        .then(() => User.findOne({ name: 'User 1' })
          .populate('portfolio.formatType')
        )
        .then(user1 => log({ user1 }))
        .then(() => User.aggregate([
          { "$match": { "name": "User 2" } },
          { "$lookup": {
            "from": FormatType.collection.name,
            "localField": "portfolio.formatType",
            "foreignField": "_id",
            "as": "formats"
          }},
          { "$project": {
            "name": 1,
            "portfolio": {
              "$map": {
                "input": "$portfolio",
                "in": {
                  "name": "$$this.name",
                  "formatType": {
                    "$arrayElemAt": [
                      "$formats",
                      { "$indexOfArray": [ "$formats._id", "$$this.formatType" ] }
                    ]
                  }
                }
              }
            }
          }}
        ]))
        .then(user2 => log({ user2 }))
        .then(() =>
          ( version >= 3.6 ) ?
            User.aggregate([
              { "$lookup": {
                "from": FormatType.collection.name,
                "let": { "portfolio": "$portfolio" },
                "as": "portfolio",
                "pipeline": [
                  { "$match": {
                    "$expr": {
                      "$in": [ "$_id", "$$portfolio.formatType" ]
                    }
                  }},
                  { "$project": {
                    "_id": {
                      "$arrayElemAt": [
                        "$$portfolio._id",
                        { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
                      ]
                    },
                    "name": {
                      "$arrayElemAt": [
                        "$$portfolio.name",
                        { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
                      ]
                    },
                    "formatType": "$$ROOT",
                  }}
                ]
              }}
            ]).then(users => log({ users })) : ''
        );
  })
  .catch(e => console.error(e))
  .then(() => mongoose.disconnect());

})()

并使用async/await语法来处理较新的NodeJS版本,包括当前的LTS v.8.x系列:

And with async/await syntax for newer NodeJS releases, including current LTS v.8.x series:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/usertest';

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const formatTypeSchema = new Schema({
  name: String
});

const portfolioSchema = new Schema({
  name: String,
  formatType: { type: Schema.Types.ObjectId, ref: 'FormatType' }
});

const userSchema = new Schema({
  name: String,
  portfolio: [portfolioSchema]
});

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

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

(async function() {

  try {

    const conn = await mongoose.connect(uri);
    let db = conn.connections[0].db;

    let { version } = await db.command({ buildInfo: 1 });
    version = parseFloat(version.match(new RegExp(/(?:(?!-).)*/))[0]);
    log(version);

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

    // Insert some things
    let [ A, B, C ] = await FormatType.insertMany(
      [ 'A', 'B', 'C' ].map(name => ({ name }))
    );

    await User.insertMany(
      [
        {
          name: 'User 1',
          portfolio: [
            { name: 'Port A', formatType: A },
            { name: 'Port B', formatType: B }
          ]
        },
        {
          name: 'User 2',
          portfolio: [
            { name: 'Port C', formatType: C }
          ]
        }
      ]
    );


    // Show plain users
    let users = await User.find();
    log({ users });

    // Get user with populate

    let user1 = await User.findOne({ name: 'User 1' })
      .populate('portfolio.formatType');

    log({ user1 });

    // Get user with $lookup
    let user2 = await User.aggregate([
      { "$match": { "name": "User 2" } },
      { "$lookup": {
        "from": FormatType.collection.name,
        "localField": "portfolio.formatType",
        "foreignField": "_id",
        "as": "formats"
      }},
      { "$project": {
        "name": 1,
        "portfolio": {
          "$map": {
            "input": "$portfolio",
            "in": {
              "name": "$$this.name",
              "formatType": {
                "$arrayElemAt": [
                  "$formats",
                  { "$indexOfArray": [ "$formats._id", "$$this.formatType" ] }
                ]
              }
            }
          }
        }
      }}
    ]);

    log({ user2 });

    // Expressive $lookup
    if ( version >= 3.6 ) {
      let users = await User.aggregate([
        { "$lookup": {
          "from": FormatType.collection.name,
          "let": { "portfolio": "$portfolio" },
          "as": "portfolio",
          "pipeline": [
            { "$match": {
              "$expr": {
                "$in": [ "$_id", "$$portfolio.formatType" ]
              }
            }},
            { "$project": {
              "_id": {
                "$arrayElemAt": [
                  "$$portfolio._id",
                  { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
                ]
              },
              "name": {
                "$arrayElemAt": [
                  "$$portfolio.name",
                  { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
                ]
              },
              "formatType": "$$ROOT",
            }}
          ]
        }}
      ]);
      log({ users })
    }

    mongoose.disconnect();    
  } catch(e) {
    console.log(e)
  } finally {
    process.exit()
  }

})()

后面的清单(如果在每个阶段都进行了注释以解释各部分),那么您至少可以通过比较来了解两种语法形式如何相互关联.

The latter listing if commented on each stage to explain the parts, and you can at least see by comparison how both forms of syntax relate to each other.

请注意,富有表现力"的 $lookup 示例仅在连接的MongoDB服务器实际支持语法的地方运行.

Note that the "expressive" $lookup example only runs where the MongoDB server connected to actually supports the syntax.

对于那些不想麻烦自己运行代码的人来说,这是输出":

And the "output" for those who cannot be bothered to run the code themselves:

Mongoose: formattypes.remove({}, {})
Mongoose: users.remove({}, {})
Mongoose: formattypes.insertMany([ { _id: 5b1601d8be9bf225554783f5, name: 'A', __v: 0 }, { _id: 5b1601d8be9bf225554783f6, name: 'B', __v: 0 }, { _id: 5b1601d8be9bf225554783f7, name: 'C', __v: 0 } ], {})
Mongoose: users.insertMany([ { _id: 5b1601d8be9bf225554783f8, name: 'User 1', portfolio: [ { _id: 5b1601d8be9bf225554783fa, name: 'Port A', formatType: 5b1601d8be9bf225554783f5 }, { _id: 5b1601d8be9bf225554783f9, name: 'Port B', formatType: 5b1601d8be9bf225554783f6 } ], __v: 0 }, { _id: 5b1601d8be9bf225554783fb, name: 'User 2', portfolio: [ { _id: 5b1601d8be9bf225554783fc, name: 'Port C', formatType: 5b1601d8be9bf225554783f7 } ], __v: 0 } ], {})
Mongoose: users.find({}, { fields: {} })
{
  "users": [
    {
      "_id": "5b1601d8be9bf225554783f8",
      "name": "User 1",
      "portfolio": [
        {
          "_id": "5b1601d8be9bf225554783fa",
          "name": "Port A",
          "formatType": "5b1601d8be9bf225554783f5"
        },
        {
          "_id": "5b1601d8be9bf225554783f9",
          "name": "Port B",
          "formatType": "5b1601d8be9bf225554783f6"
        }
      ],
      "__v": 0
    },
    {
      "_id": "5b1601d8be9bf225554783fb",
      "name": "User 2",
      "portfolio": [
        {
          "_id": "5b1601d8be9bf225554783fc",
          "name": "Port C",
          "formatType": "5b1601d8be9bf225554783f7"
        }
      ],
      "__v": 0
    }
  ]
}
Mongoose: users.findOne({ name: 'User 1' }, { fields: {} })
Mongoose: formattypes.find({ _id: { '$in': [ ObjectId("5b1601d8be9bf225554783f5"), ObjectId("5b1601d8be9bf225554783f6") ] } }, { fields: {} })
{
  "user1": {
    "_id": "5b1601d8be9bf225554783f8",
    "name": "User 1",
    "portfolio": [
      {
        "_id": "5b1601d8be9bf225554783fa",
        "name": "Port A",
        "formatType": {
          "_id": "5b1601d8be9bf225554783f5",
          "name": "A",
          "__v": 0
        }
      },
      {
        "_id": "5b1601d8be9bf225554783f9",
        "name": "Port B",
        "formatType": {
          "_id": "5b1601d8be9bf225554783f6",
          "name": "B",
          "__v": 0
        }
      }
    ],
    "__v": 0
  }
}
Mongoose: users.aggregate([ { '$match': { name: 'User 2' } }, { '$lookup': { from: 'formattypes', localField: 'portfolio.formatType', foreignField: '_id', as: 'formats' } }, { '$project': { name: 1, portfolio: { '$map': { input: '$portfolio', in: { name: '$$this.name', formatType: { '$arrayElemAt': [ '$formats', { '$indexOfArray': [ '$formats._id', '$$this.formatType' ] } ] } } } } } } ], {})
{
  "user2": [
    {
      "_id": "5b1601d8be9bf225554783fb",
      "name": "User 2",
      "portfolio": [
        {
          "name": "Port C",
          "formatType": {
            "_id": "5b1601d8be9bf225554783f7",
            "name": "C",
            "__v": 0
          }
        }
      ]
    }
  ]
}
Mongoose: users.aggregate([ { '$lookup': { from: 'formattypes', let: { portfolio: '$portfolio' }, as: 'portfolio', pipeline: [ { '$match': { '$expr': { '$in': [ '$_id', '$$portfolio.formatType' ] } } }, { '$project': { _id: { '$arrayElemAt': [ '$$portfolio._id', { '$indexOfArray': [ '$$portfolio.formatType', '$_id' ] } ] }, name: { '$arrayElemAt': [ '$$portfolio.name', { '$indexOfArray': [ '$$portfolio.formatType', '$_id' ] } ] }, formatType: '$$ROOT' } } ] } } ], {})
{
  "users": [
    {
      "_id": "5b1601d8be9bf225554783f8",
      "name": "User 1",
      "portfolio": [
        {
          "_id": "5b1601d8be9bf225554783fa",
          "name": "Port A",
          "formatType": {
            "_id": "5b1601d8be9bf225554783f5",
            "name": "A",
            "__v": 0
          }
        },
        {
          "_id": "5b1601d8be9bf225554783f9",
          "name": "Port B",
          "formatType": {
            "_id": "5b1601d8be9bf225554783f6",
            "name": "B",
            "__v": 0
          }
        }
      ],
      "__v": 0
    },
    {
      "_id": "5b1601d8be9bf225554783fb",
      "name": "User 2",
      "portfolio": [
        {
          "_id": "5b1601d8be9bf225554783fc",
          "name": "Port C",
          "formatType": {
            "_id": "5b1601d8be9bf225554783f7",
            "name": "C",
            "__v": 0
          }
        }
      ],
      "__v": 0
    }
  ]
}

这篇关于在数组中填充对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆