ChatGPT解决这个技术问题 Extra ChatGPT

在猫鼬中填充嵌套数组

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

  {
    "__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);
  });
现在是空的吗?你得到什么结果?
如果我写 ...populate('pages pages.page.components').exec...,我会得到与示例文档中所述相同的内容。什么都没有改变。
如何过滤页面内的文档?例如,我只想要带有 "__V": 1 的页面
@MahmoodHussain 请作为一个新问题提出

J
James

Mongoose 4.5 支持这个

Project.find(query)
  .populate({ 
     path: 'pages',
     populate: {
       path: 'components',
       model: 'Component'
     } 
  })
  .exec(function(err, docs) {});

您可以加入多个深度级别。

编辑 2021 年 3 月 17 日:这是库的实现,它在幕后所做的是进行另一个查询为您获取内容,然后加入内存。虽然这项工作我们真的不应该依赖。它将使您的数据库设计看起来像 SQL 表。这是昂贵的操作并且不能很好地扩展。请尝试设计您的文档以减少连接。


太棒了——干净多了!现在这是现代且正确的答案。 Documented here
@NgaNguyenDuy github.com/Automattic/mongoose/wiki/4.0-Release-Notes 说这个功能自 4.0 以来就已经存在。你可能得到了错误的查询。
@TrinhHoangNhu 我没有 4.0 发行说明,但我试过了。如果我将它作为 mongoose 4.0 运行,我的查询不会返回任何内容,但是当我升级到 4.5.8 版本时它运行良好。我的查询:gist.github.com/NgaNguyenDuy/998f7714fb768427abf5838fafa573d7
@NgaNguyenDuy 我还需要更新到 4.5.8 才能完成这项工作!
我很困惑这将如何工作,因为路径是 pages.$.page.component 而不是 pages.$.component。它如何知道查看页面对象?
P
Pier-Luc Gendreau

这对我行得通:

 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);
    });
  });

文档:Model.populate


保留“模型:'组件'”非常重要!
但不应该因为当我定义 ref 时我也定义了模型,这并不是真正的 DRY。无论如何,谢谢,它有效;)
小心精益方法。您将无法调用自定义方法,甚至无法保存返回的对象。
在我的情况下,lean() 不是必需的,但其余的效果很好。
是否可以更深地填充另一个“级别”?
s
suufi

正如其他人所指出的,Mongoose 4 支持这一点。需要注意的是,如果需要,您也可以进行更深层次的递归——尽管文档中没有说明这一点,这一点非常重要:

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

S
Shaul Hameed

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

   Project.find(query)
    .populate({ 
      path: 'pages',
      populate: [{
       path: 'components',
       model: 'Component'
      },{
        path: 'AnotherRef',
        model: 'AnotherRef',
        select: 'firstname lastname'
      }] 
   })
   .exec(function(err, docs) {});

在数组中填充路径也对我有用:populate: ['components','AnotherRef']
对我来说,在 5.5.7 版本中,Yasin 提到的数组表示法不起作用,而是在一个字符串中联系。即populate: 'components AnotherRef'
T
Tuấn Anh Đào

这是最好的解决方案:

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

所有其他答案都不必要地复杂,这应该是公认的解决方案。
这解决了 page 具有其他不可填充属性的情况。
M
MartinsOnuoha

如果您想更深入地填充另一个级别,您需要执行以下操作:

Airlines.findById(id)
      .populate({
        path: 'flights',
        populate:[
          {
            path: 'planeType',
            model: 'Plane'
          },
          {
          path: 'destination',
          model: 'Location',
          populate: { // deeper
            path: 'state',
            model: 'State',
            populate: { // even deeper
              path: 'region',
              model: 'Region'
            }
          }
        }]
      })

正在寻找同一级别的多个字段。数组方法有效。谢谢
T
Travis S

我发现在钩子之前创建一个 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)
  }
}

与我试图实现的其他一些方法相比,它是如此简单。


除非担心覆盖可能已传入的 $populate 查询。在这种情况下,您应该使用 hook.params.query.$populate = Object.assign(hook.params.query.$populate || {}, { /*在此处新建填充对象 */})
N
Nadun Liyanage

Mongoose 5.4 支持这个

Project.find(query)
.populate({
  path: 'pages.page.components',
  model: 'Component'
})

M
MD SHAYON

这就是你可以制作嵌套人口的方法

Car
  .find()
  .populate({
    path: 'partIds',
    model: 'Part',
    populate: {
      path: 'otherIds',
      model: 'Other'
    }
  })

A
Ashh

您也可以使用 $lookup 聚合来做到这一点,并且现在填充的最佳方式可能正在从 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"
  }}
])

L
Leopold Kristjansson

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

Mongoose two level population using KeystoneJs [duplicate]

exports.getStoreWithId = function (req, res) {
    Store.model
        .find()
        .populate({
            path: 'productTags productCategories',
            populate: {
                path: 'tags',
            },
        })
        .where('updateId', req.params.id)
        .exec(function (err, item) {
            if (err) return res.apiError('database error', err);
            // possibly more than one
            res.apiResponse({
                store: item,
            });
        });
};

G
Guillem Puche

对于遇到 populate 问题并希望这样做的人:

用简单的文字和快速回复(气泡)聊天

用于聊天的 4 个数据库集合:客户端、用户、房间、消息。

3 种类型的发送者的相同消息数据库结构:机器人、用户和客户端

refPath 或动态引用

填充路径和模型选项

将 findOneAndReplace/replaceOne 与 $exists 一起使用

如果获取的文档不存在,则创建一个新文档

语境

目标

将新的简单文本消息保存到数据库并使用用户或客户端数据(2 个不同的模型)填充它。将新的 quickReplies 消息保存到数据库并使用用户或客户端数据填充它。保存每条消息的发件人类型:客户端、用户和机器人。仅使用其 Mongoose 模型填充具有发件人客户端或用户的消息。 _sender 类型客户端模型是客户端,因为用户是用户。

消息架构:

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);
        }
    });

提示:

对于数据库:

每条消息本身就是一个文档。

我们不使用 refPath,而是使用 populate() 上使用的 util getSenderModel。这是因为机器人。 sender.type 可以是:拥有他的数据库的用户、拥有他的数据库的客户端和没有数据库的机器人。 refPath 需要真正的模型引用,如果没有,Mongooose 会抛出错误。

sender._id 对于用户和客户端可以是 ObjectId 类型,对于机器人来说可以是 null。

对于 API 请求逻辑:

我们替换了 quickReply 消息(Message DB 必须只有一个 quickReply,但可以有任意多的简单文本消息)。我们使用 findOneAndUpdate 代替 replaceOne 或 findOneAndReplace。

我们执行查询操作(findOneAndUpdate)和使用每个回调的填充操作。如果您不知道是使用 async/await、then()、exec() 还是 callback(err, document),这一点很重要。有关更多信息,请查看填充文档。

我们将快速回复消息替换为覆盖选项并且没有 $set 查询运算符。

如果我们没有找到快速回复,我们会创建一个新回复。您必须使用 upsert 选项告诉 Mongoose。

我们只填充一次,用于替换的消息或新保存的消息。

我们返回回调,无论我们使用 findOneAndUpdate 和 populate() 保存的消息是什么。

在填充中,我们使用 getSenderModel 创建自定义动态模型引用。我们可以使用 Mongoose 动态引用,因为 bot 的 sender.type 没有任何 Mongoose 模型。我们使用带有模型和路径选项的跨数据库填充。

我花了很多时间在这里和那里解决小问题,我希望这会对某人有所帮助! 😃


S
Saahithyan Vigneswaran

我使用以下干净的语法。这个代码块来自我的项目

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
    }
})

如果我想从结果中查询和填充

仅通过学生 id const result = await Result.find(filter).populate('student') 仅通过考试类型 const result = await Result.find(filter).populate('exam.type') 通过学生 id 和考试type const result = await Result.find(filter).populate('student Exam.type')

如果您需要更多说明,请在评论中提问


S
Samuel G

我为此挣扎了整整一天。上述解决方案均无效。在我的情况下,唯一有效的示例如下:

{
  outerProp1: {
    nestedProp1: [
      { prop1: x, prop2: y, prop3: ObjectId("....")},
      ...
    ],
    nestedProp2: [
      { prop1: x, prop2: y, prop3: ObjectId("....")},
      ...
    ]
  },
  ...
}

是执行以下操作:(假设在 fetch 之后填充 - 但在从 Model 类调用 populate 时也有效(随后是 exec))

await doc.populate({
  path: 'outerProp1.nestedProp1.prop3'
}).execPopulate()

// doc is now populated

换句话说,最外面的路径属性必须包含完整路径。没有部分完整的路径与填充属性相结合似乎有效(并且模型属性似乎不是必需的;因为它包含在架构中,所以很有意义)。我花了整整一天的时间才弄清楚这一点!不知道为什么其他示例不起作用。

(使用猫鼬 5.5.32)


R
Rafiq

用一层嵌套填充和投影回答,你可能会觉得很有趣。

https://mongoplayground.net/p/2dpeZWsXR-V

询问:

db.booking.aggregate([
  {
    "$match": {
      id: "61fdfeef678791001880da25"
    }
  },
  {
    $unwind: "$cart"
  },
  {
    "$lookup": {
      "from": "products",
      "localField": "cart.product",
      "foreignField": "id",
      "as": "prod"
    }
  },
  {
    "$unwind": "$prod"
  },
  {
    "$project": {
      id: 1,
      status: 1,
      cart: [
        {
          id: "$cart.id",
          date: "$cart.date",
          timeSlots: "$cart.timeSlots",
          product: {
            id: "$prod.id",
            name: "$prod.name",
            
          }
        }
      ],
      
    }
  }
])

D b:

db={
  "booking": [
    {
      "status": "0",
      "cart": [
        {
          "id": "61fdffc7678791001880da5f",
          "date": "2022-02-05T00:00:00.000Z",
          "product": "61fd7bc5801207001b94d949",
          "timeSlots": [
            {
              "id": "61fd7bf2801207001b94d99c",
              "spots": 1
            }
          ],
          "createdAt": "2022-02-05T04:40:39.155Z",
          "updatedAt": "2022-02-05T04:40:39.155Z"
        }
      ],
      "version": 1,
      "id": "61fdfeef678791001880da25"
    }
  ],
  "products": [
    {
      "meta": {
        "timeZone": "America/New_York"
      },
      "photos": [],
      "name": "Guide To Toronto Canada",
      "timeSlots": [
        {
          "id": "61fd7bcf801207001b94d94d",
          "discount": null,
          "endTime": "2022-02-05T03:01:00.000Z",
          "spots": null,
          "startTime": "2022-02-04T14:00:00.000Z"
        },
        {
          "id": "61fd7bf2801207001b94d99c",
          "discount": null,
          "endTime": "2022-02-04T20:18:00.000Z",
          "spots": 15,
          "startTime": "2022-02-04T19:18:00.000Z"
        },
        
      ],
      "mrp": 20,
      "id": "61fd7bc5801207001b94d949"
    }
  ]
}

E
Eric Aya

我尝试使用最新版本的猫鼬 5.10

请考虑以下模式定义的用例,然后查看如何填充

const jobsSchema: Schema = new Schema({
    employerId:String
}, {strict : false})

jobsSchema.virtual('employer', {
    ref: 'Employer',
    localField: 'employerId',
    foreignField: '_id',
    justOne: true 
});

另一个模型是工作视图

const jobsViewSchema: Schema = new Schema({
    jobId:String
}, {strict : false})

jobsViewSchema.virtual('job', {
    ref: 'Jobs',
    localField: 'jobId',
    foreignField: '_id',
    justOne: true
});

现在填充

this.JobViewModel.find(query).populate({ 
     path: 'job', 
     populate: ['employer', 'Virtual2', 'Virtual3'] 
})

这将完美地填充整个对象。


K
Khalid

删除文档参考

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);
});