Typegoose 模型和多对多关系

Posted

技术标签:

【中文标题】Typegoose 模型和多对多关系【英文标题】:Typegoose Models and Many to Many Relationships 【发布时间】:2021-02-21 14:08:50 【问题描述】:

所以我正在使用 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”的原因,但部门不保存成员数组(因为缺少@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】:

所以,这不容易找到,希望这个答案对其他人有所帮助。我仍然认为有必要记录更多的基本内容——比如在对象被删除时删除对对象的引用。就像,任何有参考的人都需要这个,但在任何文档(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-hook,只要把它放在你的模型前面:

@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. 时会调用它。部门模型.remove()。不要使用它,因为 remove() 已被弃用。请改用“deleteOne()”。用 "const depId = this.getFilter()["_id"];"您可以访问要在操作中删除的文档的 ID。

【讨论】:

感谢@Alex,但我正在尝试做同样的事情,但将对象嵌入为对象数组。您所说的 typegoose 文档需要提供有关如何完成工作的完整示例。

以上是关于Typegoose 模型和多对多关系的主要内容,如果未能解决你的问题,请参考以下文章

MySQL——一对多和多对多简单模型建表

MySQL——一对多和多对多简单模型建表

用于简单数据模型和多对多关系的 Optimal Entity Framework 4 查询

核心数据和多对多

Cocoa,使用集合运算符和多对多关系属性进行绑定

如何判断一对一对多和多对多的关系