Mongoose 使用 getter 函数填充虚拟字段不起作用

kri*_*hna 1 mongoose mongodb

我正在使用 getter 函数填充 mongoose 模式中的虚拟字段。它应该在没有任何错误的情况下填充该字段,但它正在抛出错误

MongooseError:如果您要填充虚拟,则必须设置 localField 和 foreignField 选项

当我们使用 getter 填充字段时,不需要 localfield 和 foriegn 字段

const DashboardSchema = new Schema({
    userId: {
        type: SchemaTypes.ObjectId
    }
}, { toJSON: { virtuals: true } });

DashboardSchema.virtual('TopReports').get(function () {
   return TopReports.find({ userId: this.userId }).sort('-date').limit(10);
})
Run Code Online (Sandbox Code Playgroud)

Nei*_*unn 8

如果你想要一个“虚拟填入”具有sortlimit那你是怎么做的实际上并不是。您创建的只是一个“虚拟 getter”,它实际上返回异步函数的结果。您可以使用它,但是管理解决返回的问题要困难得多,Promise而且它实际上与 无关populate(),这就是您引发错误的地方。

这样做也有不同的选择。

猫鼬虚拟种群

对于最接近您尝试的内容,您想要的是这样的:

const dashboardSchema = new Schema({
  userId: Schema.Types.ObjectId
},
{
  toJSON: { virtuals: true },
  toObject: { virtuals: true }
});

dashboardSchema.virtual('TopReports', {
  ref: 'Report',
  localField: 'userId',
  foreignField: 'userId',
  options: { sort: { date: -1 }, limit: 10 } // optional - could add with populate
});

const reportSchema = new Schema({
  userId: Schema.Types.ObjectId,
  seq: Number,
  date: Date
});

const Dashboard = mongoose.model('Dashboard', dashboardSchema);
const Report = mongoose.model('Report', reportSchema);
Run Code Online (Sandbox Code Playgroud)

这实际上与“Populate Virtuals”上的文档示例几乎相同,因为那里的给定示例还包括options与将选项传递给 populate 方法本身相同的内容。当您设置options时,virtual您只需要像这样调用:

let result = await Dashboard.findOne().populate('TopReports');
Run Code Online (Sandbox Code Playgroud)

在执行时,默认设置sortlimit自动应用于此“虚拟”字段populate()。如果您选择不包括options您只需手动添加选项:

let result2 = await Dashboard.findOne().populate({
  path: 'TopReports',
  options: { sort: '-date', limit: 5 }
});
log(result2);
Run Code Online (Sandbox Code Playgroud)

重要提示-设置optionsvirtual始终覆盖任何options传递到populate()如上图所示。如果你想options在不同的请求上使用不同的,那么你调用上面的方法而不是定义virtual附加到模式的方法。

这就是你真正需要做的。当然,定义包括localFieldforeignField以及refall 以便populate()调用知道从哪里获取数据以及与哪些字段相关。还有一个选项 justOne可以区分单数和Array结果,以及一些其他选项。

MongoDB $查找

这里的另一个选项是 MongoDB 基本上具有相同的内置功能,除了这是单个请求,而不是populate()实际上是多个请求,以便从单独的集合中返回数据:

   let result = await Dashboard.aggregate([
      { "$lookup": {
        "from": Report.collection.name,
        "let": { "userId": "$userId" },
        "pipeline": [
          { "$match": { "$expr": { "$eq": [ "$userId", "$$userId" ] } } },
          { "$sort": { "date": -1 } },
          { "$limit": 10 }
        ],
        "as": "TopReports"
      }}
    ]);
Run Code Online (Sandbox Code Playgroud)

所以这是相比于“多”作为一个单一的响应一个请求find()实际上由发出的请求populate()。这是相同的结果,只是使用了“子管道”形式,$lookup以便将$sort$limit应用于返回的相关项数组。

通过一些工作,您甚至可以检查 mongoose 模式中的模式定义(包括已定义的 virtual )并构造相同的$lookup语句。在 Mongoose 中填充后的查询上有此“模式检查”的基本演示。


所以这取决于哪个最适合您的需求。我建议同时尝试甚至对应用程序性能进行基准测试。

作为完整的演示,这里有一个示例列表。它插入 20 个东西,只返回数组中最近的 10 个结果:

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

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

mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
mongoose.set('debug', true);

const dashboardSchema = new Schema({
  userId: Schema.Types.ObjectId
},
{
  toJSON: { virtuals: true },
  toObject: { virtuals: true }
});

dashboardSchema.virtual('TopReports', {
  ref: 'Report',
  localField: 'userId',
  foreignField: 'userId',
  options: { sort: { date: -1 }, limit: 10 } // optional - could add with populate
});

const reportSchema = new Schema({
  userId: Schema.Types.ObjectId,
  seq: Number,
  date: Date
});

const Dashboard = mongoose.model('Dashboard', dashboardSchema);
const Report = mongoose.model('Report', reportSchema);

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

(async function() {

  try {

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

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

    // Insert some things
    let { userId } = await Dashboard.create({ userId: new ObjectId() });

    const oneDay = ( 1000 * 60 * 60 * 24 );
    const baseDate = Date.now() - (oneDay * 30); // 30 days ago

    await Report.insertMany(
      [ ...Array(20)]
        .map((e,i) => ({ userId, seq: i+1, date: baseDate + ( oneDay * i ) }))
    );

    // Virtual populate
    let popresult = await Dashboard.findOne().populate('TopReports');
    log(popresult);


    // Aggregate $lookup
    let result = await Dashboard.aggregate([
      { "$lookup": {
        "from": Report.collection.name,
        "let": { "userId": "$userId" },
        "pipeline": [
          { "$match": { "$expr": { "$eq": [ "$userId", "$$userId" ] } } },
          { "$sort": { "date": -1 } },
          { "$limit": 10 }
        ],
        "as": "TopReports"
      }}
    ]);

    log(result);


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

})()
Run Code Online (Sandbox Code Playgroud)

和输出:

Mongoose: dashboards.deleteMany({}, {})
Mongoose: reports.deleteMany({}, {})
Mongoose: dashboards.insertOne({ _id: ObjectId("5cce3d9e16302f32acb5c572"), userId: ObjectId("5cce3d9e16302f32acb5c571"), __v: 0 })
Mongoose: reports.insertMany([ { _id: 5cce3d9e16302f32acb5c573, userId: 5cce3d9e16302f32acb5c571, seq: 1, date: 2019-04-05T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c574, userId: 5cce3d9e16302f32acb5c571, seq: 2, date: 2019-04-06T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c575, userId: 5cce3d9e16302f32acb5c571, seq: 3, date: 2019-04-07T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c576, userId: 5cce3d9e16302f32acb5c571, seq: 4, date: 2019-04-08T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c577, userId: 5cce3d9e16302f32acb5c571, seq: 5, date: 2019-04-09T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c578, userId: 5cce3d9e16302f32acb5c571, seq: 6, date: 2019-04-10T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c579, userId: 5cce3d9e16302f32acb5c571, seq: 7, date: 2019-04-11T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c57a, userId: 5cce3d9e16302f32acb5c571, seq: 8, date: 2019-04-12T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c57b, userId: 5cce3d9e16302f32acb5c571, seq: 9, date: 2019-04-13T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c57c, userId: 5cce3d9e16302f32acb5c571, seq: 10, date: 2019-04-14T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c57d, userId: 5cce3d9e16302f32acb5c571, seq: 11, date: 2019-04-15T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c57e, userId: 5cce3d9e16302f32acb5c571, seq: 12, date: 2019-04-16T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c57f, userId: 5cce3d9e16302f32acb5c571, seq: 13, date: 2019-04-17T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c580, userId: 5cce3d9e16302f32acb5c571, seq: 14, date: 2019-04-18T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c581, userId: 5cce3d9e16302f32acb5c571, seq: 15, date: 2019-04-19T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c582, userId: 5cce3d9e16302f32acb5c571, seq: 16, date: 2019-04-20T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c583, userId: 5cce3d9e16302f32acb5c571, seq: 17, date: 2019-04-21T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c584, userId: 5cce3d9e16302f32acb5c571, seq: 18, date: 2019-04-22T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c585, userId: 5cce3d9e16302f32acb5c571, seq: 19, date: 2019-04-23T01:34:22.554Z, __v: 0 }, { _id: 5cce3d9e16302f32acb5c586, userId: 5cce3d9e16302f32acb5c571, seq: 20, date: 2019-04-24T01:34:22.554Z, __v: 0 } ], {})
Mongoose: dashboards.findOne({}, { projection: {} })
Mongoose: reports.find({ userId: { '$in': [ ObjectId("5cce3d9e16302f32acb5c571") ] } }, { sort: { date: -1 }, limit: 10, projection: {} })
{
  "_id": "5cce3d9e16302f32acb5c572",
  "userId": "5cce3d9e16302f32acb5c571",
  "__v": 0,
  "TopReports": [
    {
      "_id": "5cce3d9e16302f32acb5c586",
      "userId": "5cce3d9e16302f32acb5c571",
      "seq": 20,
      "date": "2019-04-24T01:34:22.554Z",
      "__v": 0
    },
    {
      "_id": "5cce3d9e16302f32acb5c585",
      "userId": "5cce3d9e16302f32acb5c571",
      "seq": 19,
      "date": "2019-04-23T01:34:22.554Z",
      "__v": 0
    },
    {
      "_id": "5cce3d9e16302f32acb5c584",
      "userId": "5cce3d9e16302f32acb5c571",
      "seq": 18,
      "date": "2019-04-22T01:34:22.554Z",
      "__v": 0
    },
    {
      "_id": "5cce3d9e16302f32acb5c583",
      "userId": "5cce3d9e16302f32acb5c571",
      "seq": 17,
      "date": "2019-04-21T01:34:22.554Z",
      "__v": 0
    },
    {
      "_id": "5cce3d9e16302f32acb5c582",
      "userId": "5cce3d9e16302f32acb5c571",
      "seq": 16,
      "date": "2019-04-20T01:34:22.554Z",
      "__v": 0
    },
    {
      "_id": "5cce3d9e16302f32acb5c581",
      "userId": "5cce3d9e16302f32acb5c571",
      "seq": 15,
      "date": "2019-04-19T01:34:22.554Z",
      "__v": 0
    },
    {
      "_id": "5cce3d9e16302f32acb5c580",
      "userId": "5cce3d9e16302f32acb5c571",
      "seq": 14,
      "date": "2019-04-18T01:34:22.554Z",
      "__v": 0
    },
    {
      "_id": "5cce3d9e16302f32acb5c57f",
      "userId": "5cce3d9e16302f32acb5c571",
      "seq": 13,
      "date": "2019-04-17T01:34:22.554Z",
      "__v": 0
    },
    {
      "_id": "5cce3d9e16302f32acb5c57e",
      "userId": "5cce3d9e16302f32acb5c571",
      "seq": 12,
      "date": "2019-04-16T01:34:22.554Z",
      "__v": 0
    },
    {
      "_id": "5cce3d9e16302f32acb5c57d",
      "userId": "5cce3d9e16302f32acb5c571",
      "seq": 11,
      "date": "2019-04-15T01:34:22.554Z",
      "__v": 0
    }
  ],
  "id": "5cce3d9e16302f32acb5c572"
}
Mongoose: dashboards.aggregate([ { '$lookup': { from: 'reports', let: { userId: '$userId' }, pipeline: [ { '$match': { '$expr': { '$eq': [ '$userId', '$$userId' ] } } }, { '$sort': { date: -1 } }, { '$limit': 10 } ], as: 'TopReports' } } ], {})
[
  {
    "_id": "5cce3d9e16302f32acb5c572",
    "userId": "5cce3d9e16302f32acb5c571",
    "__v": 0,
    "TopReports": [
      {
        "_id": "5cce3d9e16302f32acb5c586",
        "userId": "5cce3d9e16302f32acb5c571",
        "seq": 20,
        "date": "2019-04-24T01:34:22.554Z",
        "__v": 0
      },
      {
        "_id": "5cce3d9e16302f32acb5c585",
        "userId": "5cce3d9e16302f32acb5c571",
        "seq": 19,
        "date": "2019-04-23T01:34:22.554Z",
        "__v": 0
      },
      {
        "_id": "5cce3d9e16302f32acb5c584",
        "userId": "5cce3d9e16302f32acb5c571",
        "seq": 18,
        "date": "2019-04-22T01:34:22.554Z",
        "__v": 0
      },
      {
        "_id": "5cce3d9e16302f32acb5c583",
        "userId": "5cce3d9e16302f32acb5c571",
        "seq": 17,
        "date": "2019-04-21T01:34:22.554Z",
        "__v": 0
      },
      {
        "_id": "5cce3d9e16302f32acb5c582",
        "userId": "5cce3d9e16302f32acb5c571",
        "seq": 16,
        "date": "2019-04-20T01:34:22.554Z",
        "__v": 0
      },
      {
        "_id": "5cce3d9e16302f32acb5c581",
        "userId": "5cce3d9e16302f32acb5c571",
        "seq": 15,
        "date": "2019-04-19T01:34:22.554Z",
        "__v": 0
      },
      {
        "_id": "5cce3d9e16302f32acb5c580",
        "userId": "5cce3d9e16302f32acb5c571",
        "seq": 14,
        "date": "2019-04-18T01:34:22.554Z",
        "__v": 0
      },
      {
        "_id": "5cce3d9e16302f32acb5c57f",
        "userId": "5cce3d9e16302f32acb5c571",
        "seq": 13,
        "date": "2019-04-17T01:34:22.554Z",
        "__v": 0
      },
      {
        "_id": "5cce3d9e16302f32acb5c57e",
        "userId": "5cce3d9e16302f32acb5c571",
        "seq": 12,
        "date": "2019-04-16T01:34:22.554Z",
        "__v": 0
      },
      {
        "_id": "5cce3d9e16302f32acb5c57d",
        "userId": "5cce3d9e16302f32acb5c571",
        "seq": 11,
        "date": "2019-04-15T01:34:22.554Z",
        "__v": 0
      }
    ]
  }
]
Run Code Online (Sandbox Code Playgroud)

错误的方法

为了演示“getter”方法有什么问题,下面是一个示例列表,显示实际解析Promise每个返回对象的返回值:

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

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

mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
mongoose.set('debug', true);

const dashboardSchema = new Schema({
  userId: Schema.Types.ObjectId
},
{
  toJSON: { virtuals: true },
  toObject: { virtuals: true }
});

dashboardSchema.virtual('TopReports').get(function() {
  return Report.find({ userId: this.userId }).sort("-date").limit(10);
});

const reportSchema = new Schema({
  userId: Schema.Types.ObjectId,
  seq: Number,
  date: Date
});

const Dashboard = mongoose.model('Dashboard', dashboardSchema);
const Report = mongoose.model('Report', reportSchema);

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

(async function() {

  try {

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

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

    // Insert some things
    let { userId } = await Dashboard.create({ userId: new ObjectId() });

    const oneDay = ( 1000 * 60 * 60 * 24 );
    const baseDate = Date.now() - (oneDay * 30); // 30 days ago

    await Report.insertMany(
      [ ...Array(20)]
        .map((e,i) => ({ userId, seq: i+1, date: baseDate + ( oneDay * i ) }))
    );

    // Mimic the virtual populate with the getter
    let results = await Dashboard.find();
    for ( let r of results ) {
      let obj = { ...r.toObject() };        // copy the plain object data only
      obj.TopReports = await r.TopReports;  // Resolve the Promise
      log(obj);
    }

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

})()
Run Code Online (Sandbox Code Playgroud)

和输出:

Mongoose: dashboards.deleteMany({}, {})
Mongoose: reports.deleteMany({}, {})
Mongoose: dashboards.insertOne({ _id: ObjectId("5cce45193134aa37e88c4114"), userId: ObjectId("5cce45193134aa37e88c4113"), __v: 0 })
Mongoose: reports.insertMany([ { _id: 5cce45193134aa37e88c4115, userId: 5cce45193134aa37e88c4113, seq: 1, date: 2019-04-05T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4116, userId: 5cce45193134aa37e88c4113, seq: 2, date: 2019-04-06T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4117, userId: 5cce45193134aa37e88c4113, seq: 3, date: 2019-04-07T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4118, userId: 5cce45193134aa37e88c4113, seq: 4, date: 2019-04-08T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4119, userId: 5cce45193134aa37e88c4113, seq: 5, date: 2019-04-09T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c411a, userId: 5cce45193134aa37e88c4113, seq: 6, date: 2019-04-10T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c411b, userId: 5cce45193134aa37e88c4113, seq: 7, date: 2019-04-11T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c411c, userId: 5cce45193134aa37e88c4113, seq: 8, date: 2019-04-12T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c411d, userId: 5cce45193134aa37e88c4113, seq: 9, date: 2019-04-13T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c411e, userId: 5cce45193134aa37e88c4113, seq: 10, date: 2019-04-14T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c411f, userId: 5cce45193134aa37e88c4113, seq: 11, date: 2019-04-15T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4120, userId: 5cce45193134aa37e88c4113, seq: 12, date: 2019-04-16T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4121, userId: 5cce45193134aa37e88c4113, seq: 13, date: 2019-04-17T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4122, userId: 5cce45193134aa37e88c4113, seq: 14, date: 2019-04-18T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4123, userId: 5cce45193134aa37e88c4113, seq: 15, date: 2019-04-19T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4124, userId: 5cce45193134aa37e88c4113, seq: 16, date: 2019-04-20T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4125, userId: 5cce45193134aa37e88c4113, seq: 17, date: 2019-04-21T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4126, userId: 5cce45193134aa37e88c4113, seq: 18, date: 2019-04-22T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4127, userId: 5cce45193134aa37e88c4113, seq: 19, date: 2019-04-23T02:06:17.518Z, __v: 0 }, { _id: 5cce45193134aa37e88c4128, userId: 5cce45193134aa37e88c4113, seq: 20, date: 2019-04-24T02:06:17.518Z, __v: 0 } ], {})
Mongoose: dashboards.find({}, { projection: {} })
Mongoose: reports.find({ userId: ObjectId("5cce45193134aa37e88c4113") }, { sort: { date: -1 }, limit: 10, projection: {} })
{
  "_id": "5cce45193134aa37e88c4114",
  "userId": "5cce45193134aa37e88c4113",
  "__v": 0,
  "TopReports": [
    {
      "_id": "5cce45193134aa37e88c4128",
      "userId": "5cce45193134aa37e88c4113",
      "seq": 20,
      "date": "2019-04-24T02:06:17.518Z",
      "__v": 0
    },
    {
      "_id": "5cce45193134aa37e88c4127",
      "userId": "5cce45193134aa37e88c4113",
      "seq": 19,
      "date": "2019-04-23T02:06:17.518Z",
      "__v": 0
    },
    {
      "_id": "5cce45193134aa37e88c4126",
      "userId": "5cce45193134aa37e88c4113",
      "seq": 18,
      "date": "2019-04-22T02:06:17.518Z",
      "__v": 0
    },
    {
      "_id": "5cce45193134aa37e88c4125",
      "userId": "5cce45193134aa37e88c4113",
      "seq": 17,
      "date": "2019-04-21T02:06:17.518Z",
      "__v": 0
    },
    {
      "_id": "5cce45193134aa37e88c4124",
      "userId": "5cce45193134aa37e88c4113",
      "seq": 16,
      "date": "2019-04-20T02:06:17.518Z",
      "__v": 0
    },
    {
      "_id": "5cce45193134aa37e88c4123",
      "userId": "5cce45193134aa37e88c4113",
      "seq": 15,
      "date": "2019-04-19T02:06:17.518Z",
      "__v": 0
    },
    {
      "_id": "5cce45193134aa37e88c4122",
      "userId": "5cce45193134aa37e88c4113",
      "seq": 14,
      "date": "2019-04-18T02:06:17.518Z",
      "__v": 0
    },
    {
      "_id": "5cce45193134aa37e88c4121",
      "userId": "5cce45193134aa37e88c4113",
      "seq": 13,
      "date": "2019-04-17T02:06:17.518Z",
      "__v": 0
    },
    {
      "_id": "5cce45193134aa37e88c4120",
      "userId": "5cce45193134aa37e88c4113",
      "seq": 12,
      "date": "2019-04-16T02:06:17.518Z",
      "__v": 0
    },
    {
      "_id": "5cce45193134aa37e88c411f",
      "userId": "5cce45193134aa37e88c4113",
      "seq": 11,
      "date": "2019-04-15T02:06:17.518Z",
      "__v": 0
    }
  ],
  "id": "5cce45193134aa37e88c4114"
}
Run Code Online (Sandbox Code Playgroud)

当然Promise,每个返回的文档都需要解析getter 返回的值。相比之下,populate()使用单个请求$in返回所有结果的匹配条目find(),这将find()针对每个Dashboard文档发出一个新的,而不是find()基于结果userId中每个Dashboard文档中找到的所有值的单个。

基本上与两者相反,populate()或者$lookup您本质上是将“加入”逻辑拆分为控制流的各个部分,它实际上不属于这些部分,并且变得难以管理以及生成更多返回到服务器的请求。