TypeORM:使用自定义属性编辑多对多关系
TypeORM: edit a many-to-many relations with custom properties
如何编辑具有自定义属性的多对多关系的实体?
对于这种类型的关系,Typeorm 文档示例非常有限:
https://typeorm.io/#/many-to-many-relations/many-to-many-relations-with-custom-properties
我有 3 个表:用户、角色、user_roles
User.ts
@Entity('users')
class User {
@PrimaryGeneratedColumn('increment')
id: number;
@Column()
name: string;
@Column()
email: string;
@OneToMany(() => UserRole, userRole => userRole.user, {
cascade: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
@JoinColumn({ referencedColumnName: 'user_id' })
userRoles!: UserRole[];
}
export default User;
Role.ts
@Entity('roles')
class Role {
@PrimaryGeneratedColumn('increment')
id: number;
@Column()
role: string;
@OneToMany(() => UserRole, userRole => userRole.role, {
cascade: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
@JoinColumn({ referencedColumnName: 'role_id' })
userRoles!: UserRole[];
}
export default Role;
用户Role.ts
@Entity('user_roles')
class UserRole {
@PrimaryGeneratedColumn('increment')
id: number;
@Column()
user_id!: number;
@Column()
role_id!: number;
@CreateDateColumn()
created_at: Date;
@UpdateDateColumn()
updated_at: Date;
@ManyToOne(() => User, user => user.userRoles)
@JoinColumn({ name: 'user_id' })
user!: User;
@ManyToOne(() => Role, role => role.userRoles)
@JoinColumn({ name: 'role_id' })
role!: Role;
}
export default UserRole;
正在创建具有相关 userRoles 的用户:
const user = {
name: 'Test user',
email: 'testuser@test.com',
userRoles: [
{ role_id: 1 },
{ role_id: 2 },
{ role_id: 3 },
{ role_id: 4 }
],
};
await this.usersRepository.save(user);
更新无效,我想用新的替换所有相关的user_roles,typeorm多对多关系中有保存策略选项吗?我在其他 orms 中看到过,例如关系定义中的“替换”或“附加”选项
这就是我尝试更新的方式:
// load user
const user = await this.usersRepository.findById(user_id);
/*
returns:
User {
id: 2,
name: 'Test user',
email: 'testuser@test.com',
userRoles: [
UserRole {
id: 376,
user_id: 2,
role_id: 1,
created_at: 2020-09-18T17:26:52.000Z,
updated_at: 2020-09-18T17:26:52.000Z
},
UserRole {
id: 377,
user_id: 2,
role_id: 2,
created_at: 2020-09-18T17:26:52.000Z,
updated_at: 2020-09-18T17:26:52.000Z
},
UserRole {
id: 378,
user_id: 2,
role_id: 3,
created_at: 2020-09-18T17:26:53.000Z,
updated_at: 2020-09-18T17:26:53.000Z
},
UserRole {
id: 379,
user_id: 2,
role_id: 4,
created_at: 2020-09-18T17:26:53.000Z,
updated_at: 2020-09-18T17:26:53.000Z
}
]
}
*/
// edit user
const newRolesIds = [4, 5, 6, 7];
user.name = 'updated name';
user.email = 'updatedemail@test.com';
// isn`t it suposed to replace old user_roles and create this new ones?
user.userRoles = newRolesIds.map(roleId => {
// keep the roles that are already saved in database and are in the newRolesIds
const existingRole = user.userRoles.find(userRole => userRole.role_id === roleId);
if (existingRole) {
return existingRole;
}
// add new roles
const userRole = new UserRole();
userRole.user_id = user.id;
userRole.role_id = roleId;
return userRole;
});
/*
user after edit:
User {
id: 2,
name: 'updated name',
email: 'updatedemail@test.com',
userRoles: [
UserRole { user_id: 2, role_id: 4 },
UserRole { user_id: 2, role_id: 5 },
UserRole { user_id: 2, role_id: 6 },
]
}
*/
this.ormRepository.save(user);
保存失败,出现此 sql 错误:
这是一个奇怪的更新 sql,不识别 user_id 并且 role_id 甚至不存在于 sql 中,我是不是忘记在关系中定义一些东西?
QueryFailedError: Cannot add or update a child row: a foreign key constraint fails (`user_roles`, CONSTRAINT `UserRoleUserIdFK` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)
code: 'ER_NO_REFERENCED_ROW_2',
errno: 1452,
sqlState: '23000',
sqlMessage: 'Cannot add or update a child row: a foreign key constraint fails (`user_roles`, CONSTRAINT `UserRoleUserIdFK` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)',
query: 'UPDATE `user_roles` SET `user_id` = ?, `updated_at` = CURRENT_TIMESTAMP WHERE `id` = ?',
parameters: [ undefined, 376 ]
sql 日志显示新角色 5、6、7 的插入,之后它尝试更新应删除的角色 1、2、3 并抛出 sql 错误
根据文档,只需要从数组中删除即可删除,还是我应该手动删除?
https://typeorm.io/#/many-to-many-relations/deleting-many-to-many-relations
query: SELECT `user_roles`.`id` AS `id`, `user_roles`.`user_id` AS `user_id` FROM `user_roles` `user_roles` WHERE ((`user_roles`.`user_id` = ?)) -- PARAMETERS: [2]
query: START TRANSACTION
query: INSERT INTO `user_roles`(`id`, `user_id`, `role_id`, `created_at`) VALUES (DEFAULT, ?, ?, DEFAULT) -- PARAMETERS: [2,5]
query: SELECT `UserRole`.`id` AS `UserRole_id`, `UserRole`.`created_at` AS `UserRole_created_at` FROM `user_roles` `UserRole` WHERE `UserRole`.`id` = ? -- PARAMETERS: [7]
query: INSERT INTO `user_roles`(`id`, `user_id`, `role_id`, `created_at`) VALUES (DEFAULT, ?, ?, DEFAULT) -- PARAMETERS: [2,6]
query: SELECT `UserRole`.`id` AS `UserRole_id`, `UserRole`.`created_at` AS `UserRole_created_at` FROM `user_roles` `UserRole` WHERE `UserRole`.`id` = ? -- PARAMETERS: [8]
query: INSERT INTO `user_roles`(`id`, `user_id`, `role_id`, `created_at`) VALUES (DEFAULT, ?, ?, DEFAULT) -- PARAMETERS: [2,7]
query: SELECT `UserRole`.`id` AS `UserRole_id`, `UserRole`.`created_at` AS `UserRole_created_at` FROM `user_roles` `UserRole` WHERE `UserRole`.`id` = ? -- PARAMETERS: [9]
query: UPDATE `user_roles` SET `user_id` = ? WHERE `id` = ? -- PARAMETERS: [null,1]
query: UPDATE `user_roles` SET `user_id` = ? WHERE `id` = ? -- PARAMETERS: [null,2]
query: UPDATE `user_roles` SET `user_id` = ? WHERE `id` = ? -- PARAMETERS: [null,3]
query failed: UPDATE `user_roles` SET `user_id` = ? WHERE `id` = ? -- PARAMETERS: [null,1]
我相信你还应该对你的数据库做一个额外的查询并获取 关系 数据,然后将它们分配给“主”实体,在你的情况下是用户。如您所见,更新失败,因为 user_id
等于 NULL
。如果我是你,我会使用 Active Record pattern 和 TypeORM 进行数据库操作。它使用起来更简单实用,因为它本身管理与数据库的连接。
我发现 typeorm 目前不支持这种更新方式。默认行为是将相关记录键设置为 null 而不是删除它,在我的情况下,此行为不起作用,因为相关记录具有带索引的 FK。有一个 PR 添加此功能来删除相关记录而不是设置为空:
https://github.com/typeorm/typeorm/pull/7105
解决方法是在更新前手动删除未使用的记录
如何编辑具有自定义属性的多对多关系的实体? 对于这种类型的关系,Typeorm 文档示例非常有限: https://typeorm.io/#/many-to-many-relations/many-to-many-relations-with-custom-properties
我有 3 个表:用户、角色、user_roles
User.ts
@Entity('users')
class User {
@PrimaryGeneratedColumn('increment')
id: number;
@Column()
name: string;
@Column()
email: string;
@OneToMany(() => UserRole, userRole => userRole.user, {
cascade: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
@JoinColumn({ referencedColumnName: 'user_id' })
userRoles!: UserRole[];
}
export default User;
Role.ts
@Entity('roles')
class Role {
@PrimaryGeneratedColumn('increment')
id: number;
@Column()
role: string;
@OneToMany(() => UserRole, userRole => userRole.role, {
cascade: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
@JoinColumn({ referencedColumnName: 'role_id' })
userRoles!: UserRole[];
}
export default Role;
用户Role.ts
@Entity('user_roles')
class UserRole {
@PrimaryGeneratedColumn('increment')
id: number;
@Column()
user_id!: number;
@Column()
role_id!: number;
@CreateDateColumn()
created_at: Date;
@UpdateDateColumn()
updated_at: Date;
@ManyToOne(() => User, user => user.userRoles)
@JoinColumn({ name: 'user_id' })
user!: User;
@ManyToOne(() => Role, role => role.userRoles)
@JoinColumn({ name: 'role_id' })
role!: Role;
}
export default UserRole;
正在创建具有相关 userRoles 的用户:
const user = {
name: 'Test user',
email: 'testuser@test.com',
userRoles: [
{ role_id: 1 },
{ role_id: 2 },
{ role_id: 3 },
{ role_id: 4 }
],
};
await this.usersRepository.save(user);
更新无效,我想用新的替换所有相关的user_roles,typeorm多对多关系中有保存策略选项吗?我在其他 orms 中看到过,例如关系定义中的“替换”或“附加”选项 这就是我尝试更新的方式:
// load user
const user = await this.usersRepository.findById(user_id);
/*
returns:
User {
id: 2,
name: 'Test user',
email: 'testuser@test.com',
userRoles: [
UserRole {
id: 376,
user_id: 2,
role_id: 1,
created_at: 2020-09-18T17:26:52.000Z,
updated_at: 2020-09-18T17:26:52.000Z
},
UserRole {
id: 377,
user_id: 2,
role_id: 2,
created_at: 2020-09-18T17:26:52.000Z,
updated_at: 2020-09-18T17:26:52.000Z
},
UserRole {
id: 378,
user_id: 2,
role_id: 3,
created_at: 2020-09-18T17:26:53.000Z,
updated_at: 2020-09-18T17:26:53.000Z
},
UserRole {
id: 379,
user_id: 2,
role_id: 4,
created_at: 2020-09-18T17:26:53.000Z,
updated_at: 2020-09-18T17:26:53.000Z
}
]
}
*/
// edit user
const newRolesIds = [4, 5, 6, 7];
user.name = 'updated name';
user.email = 'updatedemail@test.com';
// isn`t it suposed to replace old user_roles and create this new ones?
user.userRoles = newRolesIds.map(roleId => {
// keep the roles that are already saved in database and are in the newRolesIds
const existingRole = user.userRoles.find(userRole => userRole.role_id === roleId);
if (existingRole) {
return existingRole;
}
// add new roles
const userRole = new UserRole();
userRole.user_id = user.id;
userRole.role_id = roleId;
return userRole;
});
/*
user after edit:
User {
id: 2,
name: 'updated name',
email: 'updatedemail@test.com',
userRoles: [
UserRole { user_id: 2, role_id: 4 },
UserRole { user_id: 2, role_id: 5 },
UserRole { user_id: 2, role_id: 6 },
]
}
*/
this.ormRepository.save(user);
保存失败,出现此 sql 错误: 这是一个奇怪的更新 sql,不识别 user_id 并且 role_id 甚至不存在于 sql 中,我是不是忘记在关系中定义一些东西?
QueryFailedError: Cannot add or update a child row: a foreign key constraint fails (`user_roles`, CONSTRAINT `UserRoleUserIdFK` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)
code: 'ER_NO_REFERENCED_ROW_2',
errno: 1452,
sqlState: '23000',
sqlMessage: 'Cannot add or update a child row: a foreign key constraint fails (`user_roles`, CONSTRAINT `UserRoleUserIdFK` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)',
query: 'UPDATE `user_roles` SET `user_id` = ?, `updated_at` = CURRENT_TIMESTAMP WHERE `id` = ?',
parameters: [ undefined, 376 ]
sql 日志显示新角色 5、6、7 的插入,之后它尝试更新应删除的角色 1、2、3 并抛出 sql 错误
根据文档,只需要从数组中删除即可删除,还是我应该手动删除? https://typeorm.io/#/many-to-many-relations/deleting-many-to-many-relations
query: SELECT `user_roles`.`id` AS `id`, `user_roles`.`user_id` AS `user_id` FROM `user_roles` `user_roles` WHERE ((`user_roles`.`user_id` = ?)) -- PARAMETERS: [2]
query: START TRANSACTION
query: INSERT INTO `user_roles`(`id`, `user_id`, `role_id`, `created_at`) VALUES (DEFAULT, ?, ?, DEFAULT) -- PARAMETERS: [2,5]
query: SELECT `UserRole`.`id` AS `UserRole_id`, `UserRole`.`created_at` AS `UserRole_created_at` FROM `user_roles` `UserRole` WHERE `UserRole`.`id` = ? -- PARAMETERS: [7]
query: INSERT INTO `user_roles`(`id`, `user_id`, `role_id`, `created_at`) VALUES (DEFAULT, ?, ?, DEFAULT) -- PARAMETERS: [2,6]
query: SELECT `UserRole`.`id` AS `UserRole_id`, `UserRole`.`created_at` AS `UserRole_created_at` FROM `user_roles` `UserRole` WHERE `UserRole`.`id` = ? -- PARAMETERS: [8]
query: INSERT INTO `user_roles`(`id`, `user_id`, `role_id`, `created_at`) VALUES (DEFAULT, ?, ?, DEFAULT) -- PARAMETERS: [2,7]
query: SELECT `UserRole`.`id` AS `UserRole_id`, `UserRole`.`created_at` AS `UserRole_created_at` FROM `user_roles` `UserRole` WHERE `UserRole`.`id` = ? -- PARAMETERS: [9]
query: UPDATE `user_roles` SET `user_id` = ? WHERE `id` = ? -- PARAMETERS: [null,1]
query: UPDATE `user_roles` SET `user_id` = ? WHERE `id` = ? -- PARAMETERS: [null,2]
query: UPDATE `user_roles` SET `user_id` = ? WHERE `id` = ? -- PARAMETERS: [null,3]
query failed: UPDATE `user_roles` SET `user_id` = ? WHERE `id` = ? -- PARAMETERS: [null,1]
我相信你还应该对你的数据库做一个额外的查询并获取 关系 数据,然后将它们分配给“主”实体,在你的情况下是用户。如您所见,更新失败,因为 user_id
等于 NULL
。如果我是你,我会使用 Active Record pattern 和 TypeORM 进行数据库操作。它使用起来更简单实用,因为它本身管理与数据库的连接。
我发现 typeorm 目前不支持这种更新方式。默认行为是将相关记录键设置为 null 而不是删除它,在我的情况下,此行为不起作用,因为相关记录具有带索引的 FK。有一个 PR 添加此功能来删除相关记录而不是设置为空: https://github.com/typeorm/typeorm/pull/7105
解决方法是在更新前手动删除未使用的记录