网站搜索

typescript - NestJS TypeORM 如何让 ManyToOne 关系的成员类型具有条件性?


我有一个实体Message,它有一个sender(User)和一个receiver,但receiver可以是User或Channel(两个实体都有messagesIn成员),我想使用TypeORM关系来实现这一点。

使用 TypeORM(Postgres)/ NestJS 做类似这样的事情是否可能(或者有没有其他常规方式实现相同的目标)?

// message.entity.ts

@Entity()
export class Message {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  content: string;

  @ManyToOne(() => User, (snd) => snd.messagesOut)
  sender: User;

  // That line doesn't compile
  @ManyToOne(() => User | Channel, (rcv) => rcv.messagesIn)
  receiver: User | Channel;
};

答案#1

你试图做的事情是不可能的,因为 TypeORM 需要确切地知道你想要填充的实体,这样它才能在正确的地方进行查询。

TypeORM 有一些机制允许你创建一个单表,所有记录,无论来自 User 还是 Channel,都将被插入其中,更多内容见此处。

但是,如果你想为每个实体拥有两个专用表(即每种类型继承的表),我建议创建另一个名为Receiver的实体,它包含User和Channel之间与消息机制相关的通用属性,以及一个名为type的属性,用于定义Receiver是User还是Channel;然后在这两个表中的每一个中引用Receiver实体。

export enum ReceiverType {
  USER = 'USER',
  CHANNEL = 'CHANNEL',
}

@Entity()
export class Receiver {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  // all common properties that you want related to messaging goes here

  @OneToMany(() => Message, (msg) => msg.receiver)
  messagesIn: Message[];

  @Column({
    type: "enum",
    enum: ReceiverType,
  })
  type: ReceiverType
}
@Entity()
export class User {
  // user properties...

  @OneToOne(() => Receiver)
  @JoinColumn()
  receiver: Receiver
}
@Entity()
export class Channel {
  // channel properties...

  @OneToOne(() => Receiver)
  @JoinColumn()
  receiver: Receiver
}

然后,在 Message 实体中,你将能够引用 Receiver:

@Entity()
export class Message {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  content: string;

  @ManyToOne(() => User, (snd) => snd.messagesOut)
  sender: User;

  @ManyToOne(() => Receiver, (rcv) => rcv.messagesIn)
  receiver: Receiver;
};

所以,当你通过存储库获取一个消息实体时,你会收到类似这样的内容:

{
  id: string;
  content: string;
  sender: { ... };
  receiver: {
    // common properties you defined...
    messagesIn: [ ... ],
    type: ReceiverType,
  }
}

掌握这些信息后,你将能够访问所需的数据,这些数据对于消息发送流程是必要的,如果你需要一些仅存在于 User 或 Channel 实体中的特定信息,你可以从 Receiver 获取 type,并通过接收者 ID 获取相应的实体,如下所示:

if (message.receiver.type === ReceiverType.USER) {
  const user = await userRepository.findOne({
    where: {
      receiver: { id: message.receiver.id },
    },
  });

  ...
}

if (message.receiver.type === ReceiverType.CHANNEL) {
  const user = await channelRepository.findOne({
    where: {
      receiver: { id: message.receiver.id },
    },
  });

  ...
}

答案#2

你不能拥有一个同时作为多个表外键的列;外键必须始终引用单个表中的特定列或列集合。你可以通过为每个表定义一个可为空的列,然后设置约束要求其中一个列为空来处理这种情况。不幸的是,我对你的混淆管理器(TypeORM)不够熟悉,因此我还将提供一个原始的 Postgres 表定义。

Create table Message( id          uuid default gen_random_uuid()
                                  primary key
                    , content     text 
                    , sender_user uuid
                                  references users(id) 
                    , rcv_user    uuid 
                                  references users(id) 
                    , rcv_channel uuid 
                                  references channels(id)                   
                    , constraint  only_user_or_channel_ck 
                                  check ( num_nulls(rcv_user,rcv_channel) = 1 )
                    ); 

当然,这对你的表、它们的键和列类型做出了假设。