自引用多对多关系类型ORM

Posted

技术标签:

【中文标题】自引用多对多关系类型ORM【英文标题】:Self-Referencing ManyToMany Relationship TypeORM 【发布时间】:2017-09-30 13:26:49 【问题描述】:

我刚刚开始使用 TypeORM,我正在努力让以下关系发挥作用:

User->Friends,而 Friend 也是一个用户对象。 但是,我的吸气剂 getFriends 和 getFriendsInverse 正在工作;我现在确实想区分这两者。换一种说法;当我执行 mysql join 时,我不想对朋友进行左连接,而对 inverseFriends 进行另一个左连接。

getter getFriends() 需要返回所有朋友,而不管我在对象的“哪一边”。

这有意义吗?

这是我的模型定义:

getFriends() 
    // This method should also return inverseFriends;
    // I do not want to return the concat version; this should
    // happen on the database/orm level
    // So I dont want: this.friends.concat(this.inverseFriends) 
    return this.friends;


@ManyToMany(type => User, user => user.friendsInverse, 
    cascadeInsert: false,
    cascadeUpdate: false,
)
@JoinTable()
friends = [];

@ManyToMany(type => User, user => user.friends, 
    cascadeInsert: true,
    cascadeUpdate: true,
    cascadeRemove: false,
)
friendsInverse = [];

我希望有人能理解我的问题 :D 谢谢 马特

【问题讨论】:

如果我的回答回答了您的问题,请将其标记为正确。在此先感谢:) 【参考方案1】:

这里是 2021 年,正在寻找相同的问题并找到一种无需自定义原始 SQL 即可解决的方法(为简单起见,提供与示例相同的模型):

import  Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn, JoinTable  from 'typeorm';

@Entity(UserModel.MODEL_NAME)
export class UserModel 
  static MODEL_NAME = 'users';

  @PrimaryGeneratedColumn()
  id?: number;

  @Column( type: 'varchar', unique: true, length: 50 )
  username: string;

  @Column( type: 'varchar', length: 50, unique: true )
  email: string;

  @ManyToMany(type => UserModel)
  @JoinTable( joinColumn:  name: 'users_id_1'  )
  friends: UserModel[];

  @Column( type: 'varchar', length: 300 )
  password: string;

这里的关键时刻是将joinColumn 设置为JoinTable

在定义多对多关系时,TypeORM 会自动创建 n-n 表 users_friends_users,其中一列名为 user_id_1,另一列名为 user_id_2(如果外键相同,它们会自动递增)

因此,从该表中选择 任何 列作为“主连接列”就足够了,它可以工作

【讨论】:

【参考方案2】:

我相信我迟到了 3 年,但迟到比以往任何时候都好。投票率最高的答案回答了这个问题,因为它只适用于树状结构和层次结构,所以如果你按照这个例子,就会发生这种情况:

               Fred
               /  \
          Albert  Laura
          /   \
       John   Foo

在这个例子中,Foo 不能和Fred 成为朋友,因为他只能有一个父母。朋友不是树状结构,它就像一张网。答案如下:

import  Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn  from 'typeorm';

@Entity(UserModel.MODEL_NAME)
export class UserModel 
  static MODEL_NAME = 'users';

  @PrimaryGeneratedColumn()
  id?: number;

  @Column( type: 'varchar', unique: true, length: 50 )
  username: string;

  @Column( type: 'varchar', length: 50, unique: true )
  email: string;

  @ManyToMany(type => UserModel)
  @JoinTable()
  friends: UserModel[];

  @Column( type: 'varchar', length: 300 )
  password: string;

这将创建一个表格,用于保存人与人之间的关系。现在是下一个重要的东西。你如何查询这个并获得用户的朋友?这并不像看起来那么容易,我已经玩了几个小时并且无法使用 TypeORM 方法甚至查询生成器来做到这一点。答案是:原始查询。这将返回一个包含用户朋友的数组:

async findFriends(id: Id): Promise<UserModel[]> 
    return await this.userORM.query(
      ` SELECT * 
        FROM users U
        WHERE U.id <> $1
          AND EXISTS(
            SELECT 1
            FROM users_friends_users F
            WHERE (F."usersId_1" = $1 AND F."usersId_2" = U.id )
            OR (F."usersId_2" = $1 AND F."usersId_1" = U.id )
            );  `,
      [id],
    );
  

users_friends_users 是 typeORM 为保存用户关系的表自动生成的名称)

【讨论】:

从这个答案中,使用空表手动实现它不是更好,就像在 SQL 中使用联结表一样?还是我错过了使用 @ManyToMany 的好处? @talfreds 使用该注释将自动为您创建联结表。这就是 ORM 及其注释的魔力。由于所有这些注释,它还将自动生成带有原始查询的迁移文件,这有点疯狂。在对我的架构进行相对较大/复杂的更改后,我尝试生成迁移,它就像一个魅力。 @talfreds 同样,一致地使用这些注释而不是自动制作部分和手动制作部分将有助于 ORM 跟踪所有内容。 啊,我的意思是这样的:***.com/questions/55253563/… 但我明白你的意思,如果你不需要额外的字段或其他东西,使用 @ManyToMany 会更好【参考方案3】:

您可以自我引用您的关系。这是一个简单的有向图示例(也就是一个节点可以有一个父节点和多个子节点)。

@Entity()
export class Service extends BaseEntity 

  @PrimaryGeneratedColumn()
  id: number;
  
  @Column()
  @Index( unique: true )
  title: string;

  @ManyToOne(type => Service, service => service.children)
  parent: Service;

  @OneToMany(type => Service, service => service.parent)
  children: Service[];

要记住的重要一点是,当使用find* 函数从数据库中读取对象时,这些关系不会自动加载。

要实际加载它们,您现在必须使用查询生成器并加入它们。 (您可以加入多个级别。)示例:

let allServices = await this.repository.createQueryBuilder('category')
  .andWhere('category.price IS NULL')
  .innerJoinAndSelect('category.children', 'product')
  .leftJoinAndSelect('product.children', 'addon')
  .getMany();

请注意我如何使用不同的名称来引用它们(categoryproductaddon)。

【讨论】:

嗨,我已经这样做了,但是在进行生产发布时,实体服务无法自行识别 我不确定这是否适用于这种情况。您在谈论树结构,但这更像是一张网。没有 1 个父母,x 个孩子。 是的,这绝对行不通。请参阅我对这篇文章原始问题的具体情况的回答。【参考方案4】:

希望有人能理解我的问题

是的,这就是问题所在。我不知道想做什么。 Friend 是一个单独的类还是仍然是 User-&gt;User[] 关系?

也许这会对您有所帮助: https://typeorm.github.io/tables-and-columns.html#closure-table

【讨论】:

以上是关于自引用多对多关系类型ORM的主要内容,如果未能解决你的问题,请参考以下文章

15)django-ORM(多对多)

Django,在 self 类中的多对多关系中,我如何在 ORM 方面相互引用?

学说:在自引用中删除实体(多对多)

多对多自引用关系

sqlalchemy中的多对多自引用关系

如何在书架中建立自引用多对多关系