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);
})
答案 0 :(得分:0)
如果您想要一个包含sort
和limit
的“虚拟人口”,那么实际上不是您要做的。您创建的只是一个“虚拟获取器”,实际上是在返回异步函数的结果。您可以使用它们,但要解决返回的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);
这实际上与"Populate Virtuals"上的文档示例几乎相同,因为给定的示例中还包含options
,这与将选项传递给填充方法本身相同。在options
中设置virtual
时,您只需要这样调用:
let result = await Dashboard.findOne().populate('TopReports');
在执行sort
时,为limit
和populate()
设置的默认值将自动应用于此“虚拟”字段。如果您选择不包括options
,则只需手动添加选项:
let result2 = await Dashboard.findOne().populate({
path: 'TopReports',
options: { sort: '-date', limit: 5 }
});
log(result2);
重要-在
options
中设置virtual
将始终覆盖传递给options
的任何populate()
如上所示。如果要对不同的请求使用不同的options
,则应按上述方法调用,而不要在附加到模式的virtual
方法上进行定义。
这就是您真正需要做的。当然,定义包括localField
和foreignField
以及ref
,因此populate()
调用知道从何处获取数据以及与哪些字段相关。还有一个可选 justOne
,用于区分单数和Array
结果,以及一些其他选项。
这里的另一种选择是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"
}}
]);
因此,与find()
实际发出的“许多” populate()
请求相比,这是一个具有单个响应的请求。结果相同,只是使用了$lookup
的“子管道”形式,以便将$sort
和$limit
应用于返回的相关项数组。
只需做一些工作,您甚至可以从猫鼬模式中检查模式定义(包括已定义的virtual),并构造相同的$lookup
语句。 Querying after populate in 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()
}
})()
输出:
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
}
]
}
]
仅是为了演示“ 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()
}
})()
并输出:
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"
}
当然,对于每个返回的文档,都需要解析getter返回的Promise
。相比之下,populate()
使用$in
以便通过单个请求返回find()
中所有结果的匹配条目,这将为每个{发出新的find()
基于{1}}文档,而不是基于结果中每个Dashboard
文档中找到的所有find()
值的单个userId
。
与Dashboard
或populate()
基本上相反,您实际上是在将“ join”逻辑拆分成控制流的一部分,该部分实际上不属于该部分,并且也变得难以管理生成更多的请求返回服务器。