TypeORM : связь с предложением where, определенным в сущности

#node.js #typescript #typeorm

Вопрос:

Я хочу создать объект заказа на продажу, который имеет 2 разных отношения к одному и тому же объекту, но с разными критериями.

Конечная цель состоит в том, чтобы получить такие адреса:

 const salesOrder = await SalesOrder.findOne(1, {
    relations: ['receiverAddress', 'senderAddress']
});
console.log(salesOrder.receiverAddress)
console.log(salesOrder.senderAddress)
 

Я не могу понять, как фильтровать отношения адресов по типу в сущности SalesOrder.

Я хочу сделать что-то подобное:

 // SalesOrder.ts
@Entity()
export class SalesOrder extends BaseEntity {
  @Column()
  @PrimaryGeneratedColumn()
  id: number

  @OneToMany(SalesOrderAddress, salesOrderAddress => salesOrderAddress, {
    where: {
      type: 'receiver' // join condition salesOrderAddress.type = 'receiver'
    }
  })
  receiverAddress: SalesOrderAddress

  @OneToMany(SalesOrderAddress, salesOrderAddress => salesOrderAddress, {
    where: {
      type: 'sender' // join condition salesOrderAddress.type = 'sender'
    }
  })
  senderAddress: SalesOrderAddress
}
 

Проблема : where предложения не обрабатываются в декораторах typeorm.

Можно ли получить что-то подобное (без использования построителя запросов)?

Я знаю, что вместо этого я могу использовать 2 отношения OneToOne, но это означает, что база данных заказов на продажу содержит два внешних ключа : receiverAddressId и senderAddressId. Я предпочитаю использовать отношения OneToMany, так как это позволяет иметь только один внешний ключ на адресе продавца (например, идентификатор продавца).

Ответ №1:

В вашем случае я бы рассмотрел возможность создания пользовательских методов для получения желаемой информации. Проверьте этот пример:

 import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne } from 'typeorm';

@Entity({ name: 'Address' })
export class Address extends BaseEntity {
  @PrimaryGeneratedColumn({ type: 'int' })
  id: number;

  @Column({ type: 'varchar', length: 20 })
  type: 'receiver' | 'sender';

  @Column({ type: 'nvarchar', length: 1024 })
  value: string;

  // Put the prop that connects this entity to SalesOrder
  @ManyToOne(SalesOrder, ref => ref.addresses)
  salesOrder: SalesOrder;
}

@Entity({ name: 'SalesOrder' })
export class SalesOrder extends BaseEntity {
  @PrimaryGeneratedColumn({ type: 'int' })
  id: number;

  // Put the prop that connects this entity to Address
  @OneToMany(Address, ref => ref.salesOrder)
  addresses: Address[];

  // Create a generic method to filter
  private static getTypeAddresses(id: number, type: 'receiver' | 'sender'): Promise<Address[]> {
    if (typeof id !== 'number') {
      throw new Error('The entity's id must be a number');
    }

    return Address
      .createQueryBuilder('Address')
      .select([ 'Address' ])
      .innerJoin(
        'Address.salesOrder',
        'SalesOrder',
        'SalesOrder.id = :id',
        { id: id }
      )
      .where(
        'type = :type',
        { type }
       )
      .getMany();
  }

  // Later, create your methods to obtain easifully your address
  static getReceiverAddresses(id: number): Promise<Address[]> {
    return SalesOrder.getTypeAddresses(id, 'receiver');
  }

  static getSenderAddresses(id: number): Promise<Address[]> {
    return SalesOrder.getTypeAddresses(id, 'sender');
  }

  getReceiverAddresses(): Promise<Address[]> {
    return SalesOrder.getTypeAddresses(this.id, 'receiver');
  }

  getSenderAddresses(): Promise<Address[]> {
    return SalesOrder.getTypeAddresses(this.id, 'sender');
  }
}
 

Пример использования:

 const salesOrder = await SalesOrder.findOne({ id: 1 });
console.log(await salesOrder.getReceiverAddresses());
console.log(await salesOrder.getSenderAddresses());
 

Если вы не хотите добавлять методы в класс вашей сущности, подумайте о создании класса контроллера или другого более удобного процесса. Например:

 import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne } from 'typeorm';

@Entity({ name: 'Address' })
export class Address extends BaseEntity {
  @PrimaryGeneratedColumn({ type: 'int' })
  id: number;

  @Column({ type: 'varchar', length: 20 })
  type: 'receiver' | 'sender';

  @Column({ type: 'nvarchar', length: 1024 })
  value: string;

  // Put the prop that connects this entity to SalesOrder
  @ManyToOne(SalesOrder, ref => ref.addresses)
  salesOrder: SalesOrder;
}

@Entity({ name: 'SalesOrder' })
export class SalesOrder extends BaseEntity {
  @PrimaryGeneratedColumn({ type: 'int' })
  id: number;

  // Put the prop that connects this entity to Address
  @OneToMany(Address, ref => ref.salesOrder)
  addresses: Address[];
}

export class SalesOrderCtrl extends SalesOrder {
  private static getTypeAddresses(id: number, type: 'receiver' | 'sender'): Promise<Address[]> {
    if (typeof id !== 'number') {
      throw new Error('The entity's id must be a number');
    }

    return Address
      .createQueryBuilder('Address')
      .select([ 'Address' ])
      .innerJoin(
        'Address.salesOrder',
        'SalesOrder',
        'SalesOrder.id = :id',
        { id: id }
      )
      .where(
        'type = :type',
        { type }
       )
      .getMany();
  }

  static getReceiverAddresses(id: number): Promise<Address[]> {
    return SalesOrder.getTypeAddresses(id, 'receiver');
  }

  static getSenderAddresses(id: number): Promise<Address[]> {
    return SalesOrder.getTypeAddresses(id, 'sender');
  }

  getReceiverAddresses(): Promise<Address[]> {
    return SalesOrder.getTypeAddresses(this.id, 'receiver');
  }

  getSenderAddresses(): Promise<Address[]> {
    return SalesOrder.getTypeAddresses(this.id, 'sender');
  }
}
 

Пример использования:

 const salesOrder = await SalesOrderCtrl.findOne({ id: 1 });
console.log(await salesOrder.getReceiverAddresses());
console.log(await salesOrder.getSenderAddresses());