Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/mongodb/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Node.js 在mongoose中填充嵌套数组_Node.js_Mongodb_Mongoose - Fatal编程技术网

Node.js 在mongoose中填充嵌套数组

Node.js 在mongoose中填充嵌套数组,node.js,mongodb,mongoose,Node.js,Mongodb,Mongoose,如何在示例文档中填充“组件”: { "__v": 1, "_id": "5252875356f64d6d28000001", "pages": [ { "__v": 1, "_id": "5252875a56f64d6d28000002", "page": { "components": [ "525287a01877a68528000001"

如何在示例文档中填充“组件”:

  {
    "__v": 1,
    "_id": "5252875356f64d6d28000001",
    "pages": [
      {
        "__v": 1,
        "_id": "5252875a56f64d6d28000002",
        "page": {
          "components": [
            "525287a01877a68528000001"
          ]
        }
      }
    ],
    "author": "Book Author",
    "title": "Book Title"
  }
这是我的JS,我在这里通过Mongoose获取文档:

  Project.findById(id).populate('pages').exec(function(err, project) {
    res.json(project);
  });
这对我很有用:

 Project.find(query)
  .lean()
  .populate({ path: 'pages' })
  .exec(function(err, docs) {

    var options = {
      path: 'pages.components',
      model: 'Component'
    };

    if (err) return res.json(500);
    Project.populate(docs, options, function (err, projects) {
      res.json(projects);
    });
  });
文档:

删除文档参考

if (err) {
    return res.json(500);
}
Project.populate(docs, options, function (err, projects) {
    res.json(projects);
});
这对我有用

if (err) {
    return res.json(500);
}
Project.populate(options, function (err, projects) {
    res.json(projects);
});

Mongoose4.5支持这一点

Project.find(query)
  .populate({ 
     path: 'pages',
     populate: {
       path: 'components',
       model: 'Component'
     } 
  })
  .exec(function(err, docs) {});
你可以加入多个深层次


《编辑》2021年3月17日:这是图书馆的实现,它在幕后做的是做另一个查询,为你取回东西,然后加入内存。虽然这项工作,但我们真的不应该依靠。它将使您的数据库设计看起来像SQL表。这是一项成本高昂的操作,而且不能很好地扩展。请尝试设计您的文档以减少加入。

正如其他人所指出的,
Mongoose 4
支持此功能。重要的是要注意,如果需要,您也可以递归到一个以上的级别,尽管文档中没有说明:

Project.findOne({name: req.query.name})
    .populate({
        path: 'threads',
        populate: {
            path: 'messages', 
            model: 'Message',
            populate: {
                path: 'user',
                model: 'User'
            }
        }
    })

我发现这非常有助于在钩子之前创建feathersjs来填充2 ref级别的深度关系。猫鼬的模型只是简单地有

tables = new Schema({
  ..
  tableTypesB: { type: Schema.Types.ObjectId, ref: 'tableTypesB' },
  ..
}
tableTypesB = new Schema({
  ..
  tableType: { type: Schema.Types.ObjectId, ref: 'tableTypes' },
  ..
}
然后在挂钩之前的feathersjs中:

module.exports = function(options = {}) {
  return function populateTables(hook) {
    hook.params.query.$populate = {
      path: 'tableTypesB',
      populate: { path: 'tableType' }
    }

    return Promise.resolve(hook)
  }
}

与我试图实现这一点的其他一些方法相比,它非常简单。

您可以像这样填充多个嵌套文档

   Project.find(query)
    .populate({ 
      path: 'pages',
      populate: [{
       path: 'components',
       model: 'Component'
      },{
        path: 'AnotherRef',
        model: 'AnotherRef',
        select: 'firstname lastname'
      }] 
   })
   .exec(function(err, docs) {});
您也可以使用聚合来实现这一点,而且可能现在最好的填充方式是从mongo中消失

Project.aggregate([
  { "$match": { "_id": mongoose.Types.ObjectId(id) } },
  { "$lookup": {
    "from": Pages.collection.name,
    "let": { "pages": "$pages" },
    "pipeline": [
      { "$match": { "$expr": { "$in": [ "$_id", "$$pages" ] } } },
      { "$lookup": {
        "from": Component.collection.name,
        "let": { "components": "$components" },
        "pipeline": [
          { "$match": { "$expr": { "$in": [ "$_id", "$$components" ] } } },
        ],
        "as": "components"
      }},
    ],
    "as": "pages"
  }}
])

我通过另一个特定于KeystoneJS但标记为重复的问题找到了这个问题。如果这里有人可能在寻找Keystone的答案,我就是这样在Keystone中进行深度填充查询的


对于有
填充问题且也想执行此操作的人:

  • 通过简单文本和快速回复聊天(气泡)
  • 4个用于聊天的数据库集合:
    客户端
    用户
    聊天室
    消息
  • 相同的消息数据库结构适用于3种类型的发送者:bot、用户和客户端
  • refPath
  • 使用
    路径
    模型
    选项填充
  • findOneAndReplace
    /
    replaceOne
    $exists
  • 如果提取的文档不存在,则创建新文档

上下文

目标

  • 将新的简单文本消息保存到数据库中,并使用用户或客户端数据(两种不同型号)填充它
  • 将新的quickReplies消息保存到数据库,并使用用户或客户端数据填充它
  • 将每条消息保存为其发件人类型:
    客户端
    用户
    &
    机器人
  • 仅使用Mongoose模型填充具有发件人
    客户端
    用户
    的邮件_发送方类型的客户端型号为
    客户端
    ,用户类型为
    用户
    消息架构

    const messageSchema = new Schema({
        room: {
            type: Schema.Types.ObjectId,
            ref: 'rooms',
            required: [true, `Room's id`]
        },
        sender: {
             _id: { type: Schema.Types.Mixed },
            type: {
                type: String,
                enum: ['clients', 'users', 'bot'],
                required: [true, 'Only 3 options: clients, users or bot.']
            }
        },
        timetoken: {
            type: String,
            required: [true, 'It has to be a Nanosecond-precision UTC string']
        },
        data: {
            lang: String,
            // Format samples on https://docs.chatfuel.com/api/json-api/json-api
            type: {
                text: String,
                quickReplies: [
                    {
                        text: String,
                        // Blocks' ids.
                        goToBlocks: [String]
                    }
                ]
            }
        }
    
    mongoose.model('messages', messageSchema);
    

    解决方案

    我的服务器端API请求

    我的代码

    实用程序函数(在
    chatUtils.js
    文件上),用于获取要保存的消息类型:

    /**
     * We filter what type of message is.
     *
     * @param {Object} message
     * @returns {string} The type of message.
     */
    const getMessageType = message => {
        const { type } = message.data;
        const text = 'text',
            quickReplies = 'quickReplies';
    
        if (type.hasOwnProperty(text)) return text;
        else if (type.hasOwnProperty(quickReplies)) return quickReplies;
    };
    
    /**
     * Get the Mongoose's Model of the message's sender. We use
     * the sender type to find the Model.
     *
     * @param {Object} message - The message contains the sender type.
     */
    const getSenderModel = message => {
        switch (message.sender.type) {
            case 'clients':
                return 'clients';
            case 'users':
                return 'users';
            default:
                return null;
        }
    };
    
    module.exports = {
        getMessageType,
        getSenderModel
    };
    
    
    我的服务器端(使用Nodejs)获取保存消息的请求:

    app.post('/api/rooms/:roomId/messages/new', async (req, res) => {
            const { roomId } = req.params;
            const { sender, timetoken, data } = req.body;
            const { uuid, state } = sender;
            const { type } = state;
            const { lang } = data;
    
            // For more info about message structure, look up Message Schema.
            let message = {
                room: new ObjectId(roomId),
                sender: {
                    _id: type === 'bot' ? null : new ObjectId(uuid),
                    type
                },
                timetoken,
                data: {
                    lang,
                    type: {}
                }
            };
    
            // ==========================================
            //          CONVERT THE MESSAGE
            // ==========================================
            // Convert the request to be able to save on the database.
            switch (getMessageType(req.body)) {
                case 'text':
                    message.data.type.text = data.type.text;
                    break;
                case 'quickReplies':
                    // Save every quick reply from quickReplies[].
                    message.data.type.quickReplies = _.map(
                        data.type.quickReplies,
                        quickReply => {
                            const { text, goToBlocks } = quickReply;
    
                            return {
                                text,
                                goToBlocks
                            };
                        }
                    );
                    break;
                default:
                    break;
            }
    
            // ==========================================
            //           SAVE THE MESSAGE
            // ==========================================
            /**
             * We save the message on 2 ways:
             * - we replace the message type `quickReplies` (if it already exists on database) with the new one.
             * - else, we save the new message.
             */
            try {
                const options = {
                    // If the quickRepy message is found, we replace the whole document.
                    overwrite: true,
                    // If the quickRepy message isn't found, we create it.
                    upsert: true,
                    // Update validators validate the update operation against the model's schema.
                    runValidators: true,
                    // Return the document already updated.
                    new: true
                };
    
                Message.findOneAndUpdate(
                    { room: roomId, 'data.type.quickReplies': { $exists: true } },
                    message,
                    options,
                    async (err, newMessage) => {
                        if (err) {
                            throw Error(err);
                        }
    
                        // Populate the new message already saved on the database.
                        Message.populate(
                            newMessage,
                            {
                                path: 'sender._id',
                                model: getSenderModel(newMessage)
                            },
                            (err, populatedMessage) => {
                                if (err) {
                                    throw Error(err);
                                }
    
                                res.send(populatedMessage);
                            }
                        );
                    }
                );
            } catch (err) {
                logger.error(
                    `#API Error on saving a new message on the database of roomId=${roomId}. ${err}`,
                    { message: req.body }
                );
    
                // Bad Request
                res.status(400).send(false);
            }
        });
    
    提示

    const messageSchema = new Schema({
        room: {
            type: Schema.Types.ObjectId,
            ref: 'rooms',
            required: [true, `Room's id`]
        },
        sender: {
             _id: { type: Schema.Types.Mixed },
            type: {
                type: String,
                enum: ['clients', 'users', 'bot'],
                required: [true, 'Only 3 options: clients, users or bot.']
            }
        },
        timetoken: {
            type: String,
            required: [true, 'It has to be a Nanosecond-precision UTC string']
        },
        data: {
            lang: String,
            // Format samples on https://docs.chatfuel.com/api/json-api/json-api
            type: {
                text: String,
                quickReplies: [
                    {
                        text: String,
                        // Blocks' ids.
                        goToBlocks: [String]
                    }
                ]
            }
        }
    
    mongoose.model('messages', messageSchema);
    
    对于数据库:

    • 每条消息本身就是一个文档
    • 我们不使用
      refPath
      ,而是使用
      populate()
      上使用的util
      getSenderModel
      。这是因为机器人。
      sender.type
      可以是:
      users
      和他的数据库、
      clients
      bot
      没有数据库。
      refPath
      需要真正的模型引用,否则,Mongoose会抛出错误
    • sender.\u id
      可以是用户和客户端的类型
      ObjectId
      ,也可以是bot的类型
      null
    对于API请求逻辑:

    • 我们将替换
      quickReply
      消息(消息数据库必须只有一条quickReply,但要有多少条简单的文本消息就有多少条)。我们使用
      findoneandepdate
      而不是
      replaceOne
      findOneAndReplace
    • 我们执行查询操作(
      findOneAndUpdate
      )和
      populate
      操作,每个操作都有
      回调
      。如果您不知道是使用
      async/await
      then()
      exec()
      还是
      回调(err,document)
      ,这一点很重要。有关更多信息,请参阅
    • 我们使用
      覆盖
      选项替换快速回复消息,并且不使用
      $set
      查询运算符
    • 如果找不到快速回复,我们将创建一个新的快速回复。您必须使用
      upsert
      选项告诉Mongoose
    • 我们只为替换的邮件或新保存的邮件填充一次
    • 我们返回回调,不管我们用
      findOneAndUpdate
      populate()
      保存的消息是什么
    • populate
      中,我们使用
      getSenderModel
      创建一个自定义动态模型引用。我们可以使用Mongoose动态引用,因为
      bot
      sender.type
      没有任何Mongoose模型。我们使用带有
      模型
      路径
      选项的


    我花了很多时间到处解决一些小问题,我希望这能帮助别人 这是最好的解决方案:

    Car
     .find()
     .populate({
       path: 'pages.page.components'
    })
    

    我为此挣扎了整整一天。上述解决方案都不起作用。在我的案例中,唯一有效的例子如下:

    {
      outerProp1: {
        nestedProp1: [
          { prop1: x, prop2: y, prop3: ObjectId("....")},
          ...
        ],
        nestedProp2: [
          { prop1: x, prop2: y, prop3: ObjectId("....")},
          ...
        ]
      },
      ...
    }
    
    将执行以下操作:(假设在获取后填充-但在从模型类(后跟exec)调用populate时也有效)

    换句话说,最外层路径属性必须包含完整路径。没有一个部分完整的路径与填充属性相结合,似乎可以工作(而且模型属性似乎不是必需的;因为它包含在模式中,所以是有意义的)。我花了整整一天的时间才弄明白!不在南
    await doc.populate({
      path: 'outerProp1.nestedProp1.prop3'
    }).execPopulate()
    
    // doc is now populated
    
    Project.find(query)
    .populate({
      path: 'pages.page.components',
      model: 'Component'
    })
    
    const result = await Result.find(filter).populate('student exam.subject')
    
    const ExamSchema = new mongoose.Schema({
       ...
       type: String,
       ...
    })
    
    const resultSchema = new mongoose.Schema({
        ...
        exam: ExamSchema,
        student: {
            type: mongoose.Schema.Types.ObjectId,
            ref: 'User',
            required: true
        }
    })
    
    const result = await Result.find(filter).populate('student')
    
    const result = await Result.find(filter).populate('exam.type')
    
    const result = await Result.find(filter).populate('student exam.type')