Typegoose模型和多对多关系

时间:2020-11-09 16:20:04

标签: mongodb typescript mongoose typegoose

所以我要用NestJs和Typegoose构建一个具有以下模型的后端:

部门

@modelOptions({ schemaOptions: { collection: 'user_department', toJSON: { virtuals: true }, toObject: { virtuals: true }, id: false } })
export class Department {

    @prop({ required: true })
    _id: mongoose.Types.ObjectId;

    @prop({ required: true })
    name: string;

    @prop({ ref: () => User, type: String })
    public supervisors: Ref<User>[];

    members: User[];

    static paginate: PaginateMethod<Department>;
}

用户

@modelOptions({ schemaOptions: { collection: 'user' } })
export class User {

    @prop({ required: true, type: String })
    _id: string;

    @prop({ required: true })
    userName: string;

    @prop({ required: true })
    firstName: string;

    @prop({ required: true })
    lastName: string;

    [...]

    @prop({ ref: () => Department, default: [] })
    memberOfDepartments?: Ref<Department>[];

    static paginate: PaginateMethod<User>;
}

您可能会猜到,一个用户可能在多个部门中,而一个部门可以有许多成员(用户)。由于部门数量或多或少受到限制(与用户相比),我决定使用一种嵌入方式,如下所述:Two Way Embedding vs. One Way Embedding in MongoDB (Many-To-Many)。这就是用户持有数组“ memberOfDepartments”的原因,但是Department不会保存Member-array(因为缺少@prop)。

第一个问题是,当我请求Department-object时,如何查询它的成员?查询必须查找Department._id在memberOfDepartments数组中的用户。

我在这里尝试了多种方法,例如虚拟填充:https://typegoose.github.io/typegoose/docs/api/virtuals/#virtual-populate在department.model上是这样的:

@prop({
    ref: () => User,
    foreignField: 'memberOfDepartments', 
    localField: '_id', // compare this to the foreign document's value defined in "foreignField"
    justOne: false
})
public members: Ref<User>[];

但是它不会输出该属性。我的猜测是,这仅适用于一个站点上的一对多...我也尝试了set / get,但在DepartmentModel中使用UserModel遇到麻烦。

目前,我在服务中这样做是“作弊”:

async findDepartmentById(id: string): Promise<Department> {
    const res = await this.departmentModel
        .findById(id)
        .populate({ path: 'supervisors', model: User })
        .lean()
        .exec();

    res.members = await this.userModel.find({ memberOfDepartments: res._id })
        .lean()
        .exec()

    if (!res) {
        throw new HttpException(
            'No Department with the id=' + id + ' found.',
            HttpStatus.NOT_FOUND,
        );
    }
    return res;
}

..但是我认为这不是解决此问题的适当方法,因为我猜想它属于模型。

第二个问题是,如何处理部门的删除,导致我必须删除对该部门的引用。在用户中?

我知道那里有mongodb和mongoose的文档,但是我只是无法理解如何以“ typegoose方式”完成此操作,因为typegoose文档似乎对我来说非常有限。任何提示表示赞赏。

1 个答案:

答案 0 :(得分:1)

所以,这很难找出来,希望这个答案对其他人有帮助。我仍然认为有必要记录更多的基本信息-例如在删除对象时删除对对象的引用。像是,任何有参考文献的人都需要这样做,但是在任何文档(typegoose,mongoose,mongodb)中都没有给出完整的示例。

答案1:

@prop({
    ref: () => User,
    foreignField: 'memberOfDepartments', 
    localField: '_id', // compare this to the foreign document's value defined in "foreignField"
    justOne: false
})
public members: Ref<User>[];

正如所要解决的那样,这是定义虚拟的正确方法。但是我做错了,我认为不是很明显:我不得不打电话给

.populate({ path: 'members', model: User })

类似
 const res = await this.departmentModel
            .findById(id)
            .populate({ path: 'supervisors', model: User })
            .populate({ path: 'members', model: User })
            .lean()
            .exec();

如果您不执行此操作,那么您将根本看不到财产成员。我对此有疑问,因为如果您在主管等参考字段上执行此操作,至少一个数组ob objectIds。但是,如果您不伪造虚拟物品,那么您将根本没有成员域。

答案2: 我的研究得出的结论是,最好的解决方案是使用预钩。基本上,您可以定义一个函数,该函数在执行特定操作之前(如果需要,在使用后钩子之前)被调用。就我而言,该操作为“删除”,因为我想在删除文档本身之前先删除引用。 您可以使用此装饰器在typegoose中定义预钩,只需将其放在模型前面即可:

@pre<Department>('deleteOne', function (next) {
    const depId = this.getFilter()["_id"];
    getModelForClass(User).updateMany(
        { 'timeTrackingProfile.memberOfDepartments': depId },
        { $pull: { 'timeTrackingProfile.memberOfDepartments': depId } },
        { multi: true }
    ).exec();
    next();
})
export class Department {
[...]   
}

在我的研究中发现的许多建议都使用“删除”,当您致电f.e.时会被调用。 departmentmodel.remove()。不要使用它,因为remove()已过时。使用“ deleteOne()”代替。使用“ const depId = this.getFilter()[” _ id“];”您可以访问将在操作中删除的文档的ID。