如何使用Mongoose Virtuals实现考勤数据分表存储与关联查询
1. 定义Mongoose Schema
首先定义两个集合对应的Schema,配置虚拟字段关联逻辑:
const mongoose = require('mongoose'); const { Schema } = mongoose; // timesheet-data集合Schema const TimesheetDataSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: 'User', // 替换为你自己的用户Model名 required: true }, timesheetMainId: { type: Schema.Types.ObjectId, ref: 'TimesheetMain', required: true }, date: { type: String, required: true }, present: { type: Boolean, required: true } }); const TimesheetData = mongoose.model('TimesheetData', TimesheetDataSchema); // timesheet-main集合Schema const TimesheetMainSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: 'User', required: true }, week: { type: Number, required: true }, year: { type: Number, required: true } }, { // 必须开启该配置,虚拟字段才会在返回结果中显示 toJSON: { virtuals: true }, toObject: { virtuals: true } }); // 定义关联子表的虚拟字段data TimesheetMainSchema.virtual('data', { ref: 'TimesheetData', // 关联的子表Model名 localField: '_id', // 主表用于关联的字段 foreignField: 'timesheetMainId', // 子表用于关联的对应字段 justOne: false // 一对多关联,返回数组所以设为false }); // 封装静态方法:一次性写入主表+子表数据,保证事务一致性 TimesheetMainSchema.statics.createWithData = async function (mainInfo, dataList) { const session = await mongoose.startSession(); session.startTransaction(); try { // 写入主表数据 const [createdMain] = await this.create([mainInfo], { session }); // 格式化子表数据,绑定关联字段 const formattedData = dataList.map(item => ({ ...item, user: createdMain.user, timesheetMainId: createdMain._id })); // 批量写入子表数据 await TimesheetData.insertMany(formattedData, { session }); await session.commitTransaction(); session.endSession(); return createdMain; } catch (err) { await session.abortTransaction(); session.endSession(); throw err; } }; const TimesheetMain = mongoose.model('TimesheetMain', TimesheetMainSchema);
2. 数据写入示例
直接调用封装好的静态方法即可完成拆分写入,无需手动分两次插入:
// 你的原始业务数据(注意修正原始JSON中2021后面缺失的逗号) const rawData = { "user": "实际用户的ObjectId字符串", "week": 21, "year": 2021, "data": [ { "date": "01/01/21", "present": true }, { "date": "02/01/21", "present": false } ] }; // 调用写入方法 const newTimesheet = await TimesheetMain.createWithData( { user: rawData.user, week: rawData.week, year: rawData.year }, rawData.data );
3. 整合查询示例
查询时通过populate自动关联子表数据,直接返回你要求的整合结构:
// 按用户+周维度查询 const result = await TimesheetMain.findOne({ user: "目标用户ObjectId", week: 21, year: 2021 }).populate('data'); // 返回的result结构与你要求的完全一致: // { // "_id": "asf5sf44sdw3sfsf", // "user": "abc对应的用户ObjectId", // "week": 21, // "year": 2021, // "data": [ // { "_id": "Asdfsdfsdfsdfsdfsf", "date": "01/01/21", "present": true }, // { "_id": "egdfgfhfghfghfgh", "date": "02/01/21", "present": false } // ] // }
注意事项
- 如果不需要返回
__v版本字段,可以在Schema配置中添加versionKey: false - 若需要过滤子表返回的字段,可以在populate时指定:
populate('data', 'date present -_id'),示例中是只返回date、present字段,排除_id
内容的提问来源于stack exchange,提问作者raju
相关产品推荐
相关产品推荐

