NodeJS-Mongoose-如何通过ID正确连接两个集合的结果



我有以下用于跟踪习惯的模式,以及用户的进度

习惯模式

const HabitSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
title: {
type: String,
required: true,
},
type: {
type: String,
enum: ["Goal", "Limit"],
default: "Goal",
required: true,
},
dateStarted: {
type: Date,
default: Date.now,
required: true,
},
});

进度计划:每次用户点击他们在给定时间段(天、周、月等(完成了一个习惯,就会为该时间段创建一个文档,以表明已经完成。

const ProgressSchema = new mongoose.Schema({
habit: {
type: mongoose.Schema.Types.ObjectId,
ref: "Habit",
},
period: {
type: Date,
default: Date.now,
required: true,
},
value: {
type: Number,
default: 1,
required: true,
},
});

当我了解用户的所有习惯时,我还想了解每个习惯的所有进度文档。我想在返回之前加入这些对象。

以下是我尝试过的:

let habits = await Habit.find({ user: req.user.id }).sort({
dateStarted: -1,
});
habits = await Promise.all(
habits.map(async (h, i) => {
try {
const progress = await Progress.find({ habit: h._id });
return { ...h, progress };
} catch (e) {
res.status(500).send("Server Error");
}
})
);
res.json({ habits });

这会产生错误:CastError: Cast to ObjectId failed for value "undefined" at path "_id" for model "Habit"

实现这一目标的正确方法是什么?

我仍然不明白为什么会出现错误,因为我使用了wait/async,所以在检索到id之前,该行不应该运行(错误是说_id未定义,而事实显然不是(。

在尝试了很多事情之后,以下是最终奏效的:

try {
await Promise.all(
habits.map(async (h, i) => {
let progress = await Progress.find({ habit: h._id });
let newHabit = h;
newHabit._doc.progresses = progress;
return newHabit;
})
);
} catch (error) {
console.log(error);
res.status(500).send("Server Error");
}

一旦我终于让await Progress.find({ habit: h._id });工作起来,我还发现我不能用扩散运算符直接添加到猫鼬检索的对象中。对象有许多属性,数据库中的字段实际上包含在中_对象的doc属性:newHabit._doc.progresses = progress(但这与我得到的错误无关(。这让我认为有一种更好的方法可以将字段添加到mongoose.find()找到的文档中。

更好的解决方案是使用.lean((,因为您没有运行任何像.save这样的函数,这样您就不需要find查询返回的附加项。

let habits = await Habit.find({ user: req.user.id }).sort({
dateStarted: -1,
}).lean();
habits = await Promise.all(
habits.map(async (h, i) => {
try {
const progress = await Progress.find({ habit: h._id }).lean();
return { ...h, progress };
} catch (e) {
res.status(500).send("Server Error");
}
})
);
res.json({ habits });

更好的解决方案是使用ref并填充

我还没有使用Mongoose,但是,在vanilla MongoDB中,你可以使用这样的聚合框架来实现这一点:

db.Habbit.aggregate([
{ $lookup: {
from: "Progress",
localField: "_id",
foreignField: "habbit",
as: "Progresses"
} }
])

此外,您还可以添加一个步骤来丢弃除最新进展外的所有进展,而不是全部返回。然而,我将把这个留给另一个问题。

最新更新