【发布时间】:2021-06-02 15:46:05
【问题描述】:
我已经为创建新表和更新后端的 TypeORM 实体苦苦挣扎了一周。我们将 NestJS、GraphQL 和 TypeORM 与 PSQL 数据库一起使用。我们有一个生产服务器/数据库设置,其中已经保存了客户的信息。我正在尝试使用代码优先的方法向数据库添加一个新表来生成模式。在 repo 的 master 分支上,我在本地环境中启动它,并连接到一个干净的数据库。创建帐户并将信息保存到表后,我会切换到一个新分支,其中包含用于实现新表的代码,包括模块、服务、实体和解析器。如果我尝试运行此分支并连接到我在 master 上使用的同一个数据库,它将无法编译,无法生成 schema.gql 文件,并在“GraphQLModule 依赖项已初始化”处停止。我创建的这个新表与 Teams 表具有多对一关系,其中已经包含值。出于某种原因,我认为 TypeORM 无法正确更新数据库,我不知道为什么。如果我创建一个新数据库,并使用新表代码连接到分支上的新数据库,它就可以正常工作,并且不会引发任何错误。问题是我连接原来的数据库,没有报错,但是代码编译失败,不知道怎么调试。
是否有人在使用 TypeORM、Nest 和 GraphQL 将新表添加到他们的 PSQL 数据库时遇到任何问题?
这里有一些代码 sn-ps 说明了我的意思:
豁免表实体(已存在于旧数据库中)
@Entity({ name: 'waivers' })
@ObjectType()
export class WaiverEntity extends BaseEntity {
@Field(() => ID)
@PrimaryGeneratedColumn('uuid')
id: string;
@Field(() => AccountEntity)
@ManyToOne(
() => AccountEntity,
creator => creator.waivers,
{ onDelete: 'SET NULL' },
)
@JoinColumn()
creator: Promise<AccountEntity>;
@Field(() => TeamEntity)
@ManyToOne(
() => TeamEntity,
team => team.waivers,
{ onDelete: 'CASCADE' },
)
@JoinColumn()
team: Promise<TeamEntity>;
@Field(() => ID)
@Column({ nullable: true })
creatorId: string;
@Field(() => ID)
@Index()
@Column({ nullable: true })
teamId: string;
@Field()
@Column('json')
organizer: Organizer;
@Field()
@Column('json')
event: Event;
@Field()
@Column('json', { nullable: true })
eventDate: EventDate;
@Field({ nullable: true })
@Column()
includeEmergencyContact: boolean;
@Field({ nullable: true })
@Column({ nullable: true })
customerLabel: string;
@Field(() => CustomEntity, { nullable: true, defaultValue: [] })
@Column('jsonb', { nullable: true })
intensity: CustomEntity;
@Field(() => [CustomEntity], { nullable: true, defaultValue: [] })
@Column('jsonb', { nullable: true })
activities: CustomEntity[];
@Field({ defaultValue: waiverStatus.DRAFT, nullable: false })
@Column({ default: waiverStatus.DRAFT, nullable: false })
status: string;
@Field({ nullable: true })
@Column({ type: 'varchar', nullable: true })
title: string;
@Field({ nullable: true })
@Column({ nullable: true })
body: string;
@Field({ nullable: true })
@Column({ nullable: true, default: signatureDefaultContent })
signatureContent: string;
@Field(() => [String], { nullable: true })
@Column('simple-array', { nullable: true })
ageGroup: string[];
@Field(() => [AdditionalFields], { nullable: false, defaultValue: [] })
@Column('jsonb', { nullable: true })
additionalFields: AdditionalFields[];
@Field({ nullable: false })
@Column({ nullable: false })
step: number;
@Exclude()
@Field({ nullable: true })
@Column({ nullable: true, unique: true })
pdfURL: string;
@BeforeInsert()
cleanUpBeforeUpdate(): void {
// add Prefix on retrieval
if (this.organizer && this.organizer.photoURL) {
try {
const photoUrls = this.organizer.photoURL.split(
`${AWS_BUCKETS.ORGANIZATION_BUCKET_IMAGE}/`,
);
this.organizer.photoURL =
photoUrls.length > 1 ? photoUrls[1] : this.organizer.photoURL;
} catch (e) {}
}
}
@AfterLoad()
updateURLs(): void {
// add Prefix on retrieval
this.pdfURL = this.pdfURL
? `${getBucketPrefix(
AWS_BUCKETS_TYPES.WAIVER_BUCKET_FILES,
'https://',
)}/${this.pdfURL}`
: null;
if (this.organizer) {
this.organizer.photoURL = this.organizer.photoURL
? `${getBucketPrefix(
AWS_BUCKETS_TYPES.ORGANIZATION_BUCKET_IMAGE,
'https://',
)}/${this.organizer.photoURL}`
: null;
}
}
@Field({ nullable: true })
@Column({ type: 'timestamp', nullable: true })
@IsDate()
publishDate: Date;
@Field({ nullable: true })
@Column({ nullable: true, unique: true })
slug: string;
@Field(() => [DownloadEntity], { nullable: true })
@OneToMany(
() => DownloadEntity,
downloadEntity => downloadEntity.waiver,
)
@JoinColumn()
waiverDownloads: Promise<DownloadEntity[]>;
@Field({ defaultValue: 0 })
downloadCount: number;
@Field(() => [WaiverMembersEntity])
@OneToMany(
() => WaiverMembersEntity,
waiverMember => waiverMember.account,
)
accountConnection: Promise<WaiverMembersEntity[]>;
@Field(() => [WaiverConsentsEntity])
@OneToMany(
() => WaiverConsentsEntity,
waiverMember => waiverMember.waiver,
)
consent: Promise<WaiverConsentsEntity[]>;
@Field(() => [AccountEntity])
waiverMember: AccountEntity[];
@Field(() => [ParticipantsEntity])
@OneToMany(
() => ParticipantsEntity,
participant => participant.waiver,
)
participants: ParticipantsEntity[];
@Field({ defaultValue: 0 })
totalResponses: number;
@Field()
eventName: string;
@Field({ nullable: true })
@Column({ type: 'varchar', nullable: true })
smsContent: string;
@Field({ nullable: true })
@Column({ nullable: true })
smsCode: string;
@Field()
@Column({ type: 'timestamp', default: () => timeStamp })
@IsDate()
createdAt: Date;
@Field()
@Column({
type: 'timestamp',
default: () => timeStamp,
onUpdate: timeStamp,
})
@IsDate()
lastUpdatedAt: Date;
}
这里是新的实体豁免模板,它与团队表具有多对一关系,并且存在于新分支上
@Entity({ name: 'waiverTemplates' })
@ObjectType()
export class WaiverTemplateEntity extends BaseEntity {
@Field(() => ID)
@PrimaryGeneratedColumn('uuid')
id: string;
@Field(() => TeamEntity)
@ManyToOne(
() => TeamEntity,
team => team.waiverTemplates,
{ onDelete: 'CASCADE', eager: true },
)
@JoinColumn()
team: Promise<TeamEntity>;
@Field(() => ID)
@Index()
@Column({ nullable: true })
teamId: string;
@Field()
@Column('json')
event: Event;
@Field()
@Column('json')
eventDate: EventDate;
@Field({ nullable: true })
@Column({ nullable: true })
includeEmergencyContact: boolean;
@Field({ nullable: true })
@Column({ nullable: true })
customerLabel: string;
@Field(() => CustomEntity, { nullable: true, defaultValue: [] })
@Column('jsonb', { nullable: true })
intensity: CustomEntity;
@Field(() => [CustomEntity], { nullable: true, defaultValue: [] })
@Column('jsonb', { nullable: true })
activities: CustomEntity[];
@Field({ defaultValue: waiverStatus.DRAFT, nullable: false })
@Column({ default: waiverStatus.DRAFT, nullable: false })
status: string;
@Field({ nullable: true })
@Column({ type: 'varchar', nullable: true })
title: string;
@Field({ nullable: true })
@Column({ nullable: true })
body: string;
@Field({ nullable: true })
@Column({ nullable: true, default: signatureDefaultContent })
signatureContent: string;
@Field(() => [String], { nullable: true })
@Column('simple-array', { nullable: true })
ageGroup: string[];
@Field(() => [AdditionalFields], { nullable: false, defaultValue: [] })
@Column('jsonb', { nullable: true })
additionalFields: AdditionalFields[];
@Field()
eventName: string;
}
最后,这里是teams 表,它也存在于旧分支上。这是来自新分支的代码,其中包含与 WaiverTemplateEntity 的新 OneToMany 关系。
@Entity({ name: 'teams' })
@ObjectType()
export class TeamEntity extends BaseEntity {
@Field(() => ID)
@PrimaryGeneratedColumn('uuid')
id: string;
@Field()
@Column('varchar')
title: string;
@Field({ nullable: true })
@Column('varchar', { nullable: true })
taxID?: string;
@Field({ nullable: true })
@Column(simpleJSON, { nullable: true })
type: CustomEntity;
@Field({ nullable: true })
@Column('varchar', { nullable: true })
description?: string;
@Field(() => AccountEntity, { nullable: false })
@OneToOne(
() => AccountEntity,
accountEntity => accountEntity.organization,
{ nullable: true, onDelete: 'SET NULL' },
)
creator: AccountEntity;
@Field({ nullable: true })
@Column({ nullable: true })
creatorId: string;
@Field(() => BillingEntity, { nullable: true })
@OneToOne(
() => BillingEntity,
billingEntity => billingEntity.team,
{ cascade: true },
)
billingInformation: Promise<BillingEntity>;
@Field({ nullable: true })
@Column('varchar', { nullable: true })
photoURL?: string;
@Field({ defaultValue: false })
@Column({ default: false })
nonProfitFreemium: boolean;
@AfterLoad()
updateURLs(): void {
// add Prefix on retrieval
this.photoURL = this.photoURL
? `${getBucketPrefix(
AWS_BUCKETS_TYPES.ORGANIZATION_BUCKET_IMAGE,
'https://',
)}/${this.photoURL}`
: null;
}
@Field(() => [CardEntity], { nullable: true })
@OneToMany(
() => CardEntity,
cardEntity => cardEntity.holder,
{ cascade: true },
)
cards: Promise<CardEntity[]>;
@Field({ nullable: true, defaultValue: {} })
@Column(simpleJSON, { nullable: true })
location?: LocationEntity;
@Field({ nullable: true, defaultValue: {} })
@Column(simpleJSON, { nullable: true })
contact?: ContactEntity;
@Field({ nullable: true })
@Column({ nullable: true })
numberOfEmployees?: string;
@Field({ nullable: true })
@Column({ nullable: true })
stripeId?: string;
@Field()
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
@IsDate()
createdAt: Date;
@Field()
@Column({
type: 'timestamp',
default: () => 'CURRENT_TIMESTAMP(6)',
onUpdate: 'CURRENT_TIMESTAMP(6)',
})
@IsDate()
lastUpdatedAt: Date;
@Field(() => [InvitationEntity])
@OneToMany(
() => InvitationEntity,
invitationEntity => invitationEntity.team,
)
invitations: Promise<InvitationEntity[]>;
@Field(() => [WaiverEntity])
@OneToMany(
() => WaiverEntity,
waiver => waiver.team,
)
waivers: Promise<WaiverEntity[]>;
@Field({ nullable: true })
@Column({ default: () => 0 })
credits: number;
@Field({ nullable: true })
@Column({ default: () => false })
autoReload: boolean;
@Field({ nullable: true })
@Column({ default: () => 0 })
autoReloadAmount: number;
@Field({ nullable: true })
@Column({ default: () => 0 })
autoReloadMinAmount: number;
@Field({ nullable: true })
@Column({ type: 'float', default: 0.0 })
fixedWaiverPrice: number;
@Field(() => [TransactionEntity])
@OneToMany(
() => TransactionEntity,
transaction => transaction.team,
)
transactions: Promise<TransactionEntity[]>;
@Field(() => [WaiverTemplateEntity])
@OneToMany(
() => WaiverTemplateEntity,
waiverTemplate => waiverTemplate.team,
)
waiverTemplates: Promise<WaiverTemplateEntity[]>;
}
我知道表中有很多列,但需要注意的是 Teams 表和 WaiverTemplates 表之间的关系。这是我在实体中唯一更改的内容,我认为可能是我无法连接到这个新分支上的先前数据库的原因。如果您想查看我的服务、解析器或模块,请询问。我不相信它们会导致任何问题,因为如果我连接到新数据库,一切都会按预期编译和工作,不会引发任何错误。我真的只是在寻找有关如何调试此问题的任何见解。
【问题讨论】:
标签: postgresql graphql nestjs typeorm