NodeJS - TypeORM cascade insert using QueryBuilder - mysql

I'm trying to upsert an object array with a many-to-many relation to a MySQL table(s) using TypeORM query builder. Had to use the builder because repository save doesn't support upserts.
Problem is, while save cascades nested objects, QueryBuilder doesn't seem to (and RelationalQueryBuilder doesn't support upserts on MySQL).
Anyone know how I can do both? I want to both update and cascade, on an array.
First object:
#Entity()
export class Product {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: 'bigint', unique: true })
shopifyId: number;
#Column('int')
shopId: number | null;
#Column('varchar')
name: string | null;
#Column('varchar')
productType: string | null;
#Column('timestamp', { default: () => 'CURRENT_TIMESTAMP' })
updated: Date | null;
#ManyToMany(type => ProductTag, productTag => productTag.products, {
cascade: ['insert', 'update']
})
#JoinTable()
productTags: ProductTag[] | null;
}
Second object:
#Entity()
export class ProductTag {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar')
name: string | null;
#ManyToMany(type => Product, product => product.productTags)
products: Product[] | null;
}
Current upsert:
getConnection()
.createQueryBuilder()
.relation(Product, 'productTags')
.insert()
.into(Product)
.values(products)
.orUpdate({
conflict_target: ['shopifyId'],
overwrite: ['name', 'shopId', 'productType', 'updated']
})
.execute();
Thank you

Related

Typeorm SQL one to one relation using same user ID in both tables

I wanted to create one to one relation in typeorm (NestJS). Just like in the picture below.
I wanted then to signup user with email and password and create row in custom_auth and user_profile table.
relations
I cant find solution to create that type of relation. I was only able to create typical relation using foreign key (solution in typeorm docs)
export class CustomAuth {
#PrimaryGeneratedColumn()
id: number
#Column({ unique: true })
email: string
#Column()
password: string
#OneToOne(() => UserProfile, (userProfile) => userProfile.customAuth, {
cascade: true,
})
userProfile: UserProfile
}
export class UserProfile {
#PrimaryGeneratedColumn()
userId: number
#Column()
name: string
#Column({ nullable: true })
address: string
#OneToOne(() => CustomAuth, (customAuth) => customAuth.userProfile, {})
#JoinColumn()
customAuth: CustomAuth
}

TypeORM make COUNT query on table which maps two tables together

I have two entities Model and Video where Video basically store information about videos and Model store information about model. Because each video can have multiple models and each model can have multiple video entities looks like this:
How my entites and tables looks like:
// Video Entity
#Entity()
export class Video {
#PrimaryGeneratedColumn()
id?: number;
#Column({ charset: 'utf8mb4', collation: 'utf8mb4_unicode_ci' })
name: string;
#Column({ type: 'text', charset: 'utf8mb4', collation: 'utf8mb4_unicode_ci' })
description: string;
#ManyToMany((type) => Model)
#JoinTable()
models: Model[];
}
// Model Entity
#Entity()
export class Model {
#PrimaryGeneratedColumn()
id?: number;
#Column()
name: string;
#Column()
code: string;
}
Because there is relation #ManyToMany between model and video TypeORM also created one extra table for connecting this two. Table name is video_models_model and it looks like this:
+-----------+---------+
| videoId | modelId |
+===========+=========+
| 1 | 107 |
+-----------+---------+
| 2 | 142 |
+-----------+---------+
| 3 | 91 |
+-----------+---------+
What I need:
Based on modelId I need to find out COUNT() of videos.
In regular query language it would be something like:
SELECT model.*, COUNT(model_videos.videoId) as totalVideos FROM model as model
LEFT JOIN `video_models_model` `model_videos` ON `model_videos`.`modelId`=`model`.`id`
WHERE model.id = 1;
What I tried:
This is how regular query would looks like:
this.modelRepository
.createQueryBuilder('model')
.where('model.id = :id', { id: id })
.getOne();
so what I did was added to Model entity
#ManyToMany(type => Video)
#JoinTable()
videos: Video[];
and after that I tried
this.modelRepository
.createQueryBuilder('model')
.leftJoin('model.videos', 'videos')
.select('COUNT(videos)', 'totalVideos')
.where('model.id = :id', { id: id })
.getOne();
But it didn't work for me at all pluis it created one additional table named model_videos_video with modelId and videoId columns. So basically duplicate video_models_model table.
Is there any way how to make that easy query with TypeORM?
I found out that I have to change my entities to make them bi-directional so:
Video Entity:
#Entity()
export class Video {
#PrimaryGeneratedColumn()
id?: number;
#Column({ charset: 'utf8mb4', collation: 'utf8mb4_unicode_ci' })
name: string;
#Column({ type: 'text', charset: 'utf8mb4', collation: 'utf8mb4_unicode_ci' })
description: string;
#ManyToMany(type => Model, model => model.videos)
#JoinTable()
models: Model[];
}
Model Entity:
#Entity()
export class Model {
#PrimaryGeneratedColumn()
id?: number;
#Column()
name: string;
#Column()
code: string;
#ManyToMany(type => Video, video => video.models)
videos: Video[];
}
And after that I make query like this:
this.modelRepository
.createQueryBuilder('model')
.loadRelationCountAndMap('model.videoCount', 'model.videos')
.where('model.id = :id', { id: id })
.getOne();

How do I set up a OneToMany relationship in typeorm?

I have a user entity that has as a data member an array of userRoles. The users are stored in the database in a table called users, and the user_roles are stored in a table called user_role_mapping. I want to set up a one to many relationship with typeorm so that when I save a user in the database it also saves the corresponding userRoles in their respective table. I haven't been able to get it to work.
Here is my User entity
#Entity("users",{schema:"Cellphones" } )
#Index("net_id",["netId",],{unique:true})
export class User {
#PrimaryGeneratedColumn({
type:"int",
name:"id"
})
id: number;
#Column("varchar",{
nullable:false,
unique: true,
length:20,
name:"net_id"
})
netId: string;
#Column("varchar",{
nullable:true,
length:20,
name:"employee_id"
})
employeeId: string | null;
#Column("varchar",{
nullable:false,
length:50,
name:"first_name"
})
firstName: string;
#Column("varchar",{
nullable:false,
length:50,
name:"last_name"
})
lastName: string;
#Column("varchar",{
nullable:true,
length:50,
name:"title"
})
title: string | null;
#Column("tinyint",{
nullable:false,
width:1,
name:"active"
})
active: boolean;
#Column("varchar",{
nullable:true,
length:100,
name:"email"
})
email: string | null;
#Column("varchar",{
nullable:true,
length:20,
name:"phone"
})
phone: string | null;
#Column("date",{
nullable:true,
name:"term_date"
})
termDate: Date | null;
#Column("varchar",{
nullable:true,
length:10,
name:"term_department"
})
termDepartment:string | null;
#Column("date",{
nullable:true,
name:"retire_date"
})
retireDate: Date | null;
#Column("date",{
nullable:false,
name:"last_login"
})
lastLogin: Date;
#Column("varchar",{
nullable:false,
length:50,
name:"employee_status"
})
employeeStatus:string;
#Column("varchar",{
nullable:false,
length:50,
name:"department"
})
department:string;
#Column("varchar",{
nullable:true,
length:10,
name:"pay_group"
})
payGroup:string | null;
#Column("datetime",{
nullable:true,
name:"updated"
})
updated:Date | null;
#Column("int",{
nullable:true,
name:"updated_by"
})
updatedBy:number | null;
#Column("date",{
nullable:true,
name:"inactivated"
})
inactivated:Date | null;
#Column("int",{
nullable:true,
name:"inactivated_by"
})
inactivatedBy:number | null;
#OneToMany(type => UserRoleMapping, userRoleMapping => userRoleMapping.userId)
userRoles: UserRoleMapping[];
}
Here is my UserRoleMapping entity:
#ManyToOne(type => User, user => user.userRoles)
#Entity("user_role_mapping",{schema:"Cellphones" } )
export class UserRoleMapping {
constructor(id: number, userId: number, roleId: number) {}
#PrimaryGeneratedColumn({
type:"int",
name:"id"
})
id: number;
#Column({
type:"int",
name:"user_id"
})
userId: number;
#Column({
type:"int",
name:"role_id"
})
roleId: number;
}
Here is what the users table looks like:
Here is what the user_role_mapping table looks like:
Note that any column with a name starting with old_ is just used for data migration purposes and isn't relevant to this project.
There are some failures in the definition of the entity UserRoleMapping:
A relation decorater must be applied to entity properties (database columns) not at the class level.
The inverse property of the relation decorator should not target the primary key of the other table, but the navigation property (which will later hold the mapped entity object not the foreign key).
The schema option of #Entity will be ignored if using a mysql database (rather a hint).
If you prefer a custom naming strategy for columns and tables that differ from the entity property names, it can be defined globally in the TypeORM config. For minor adjustments you can derive from DefaultNamingStrategy.
Here is the code that makes the relation work.
In User:
#OneToMany(type => UserRoleMapping, userRoleMapping => userRoleMapping.user,
{ eager: true, cascade: true})
userRoles: UserRoleMapping[];
In UserRoleMapping:
#ManyToOne(type => User, user => user.userRoles)
#JoinColumn({ name: "user_id", referencedColumnName: "id"})
user: User;
The key was moving the relation decorator to the user field and defining the #JoinColumn.

NestJs update Many-To-Many relation with join table

I have two entities - Property and Owner. One Property can have a lot of Owners and Owner can have a lot of Properties. For join use property_owner table. How to update this many-to-many relation using NestJS/TypeORM?
#Entity('property')
export class Property extends EntityModel {
#Column({ length: 255, nullable: false })
name: string;
#ManyToMany(type => Owner, { cascade: true })
#JoinTable({
name: 'property_owner',
joinColumn: { name: 'propertyId', referencedColumnName: 'id'},
inverseJoinColumn: { name: 'ownerId', referencedColumnName: 'id'},
})
owners: Owner[];
}
#Entity('owner')
export class Owner extends EntityModel {
#Column({ length: 255, nullable: false })
name: string;
#ManyToMany(type => Property, { cascade: true })
#JoinTable({
name: 'property_owner',
joinColumn: { name: 'ownerId', referencedColumnName: 'id'},
inverseJoinColumn: { name: 'propertyId', referencedColumnName: 'id'},
})
properties: Property[];
}
Below my service's methods for save and update:
public create(req: Request): Promise<Dto> {
const dto: CreateDto = {
...req.body,
owners: this.formatJoinData(req.body.owners) //[1,2,3] => [{id:1},{id:2},{id:3}]
};
const entity = Object.assign(new Entity(), dto);
return this.repo.save(entity);
}
public update(req: Request): Promise<UpdateResult> {
const dto: EditDto = {
...req.body,
owners: this.formatJoinData(req.body.owners) //[1,2,3] => [{id:1},{id:2},{id:3}]
};
const id = req.params.id;
const entity = Object.assign(new Entity(), dto);
return this.repo.update(id, entity);
}
Saving new Property work fine, but when I try update property I get error
[ExceptionsHandler] column "propertyId" of relation "property" does not exist
Owners data in both cases looks like [{id:1},{id:2},{id:3}]. I think problem in save/update methods results. Save method return to us Entity with id and update method return to us UpdateResult which not contain Entity id. But may be we can transform/additionally define this value somewhere...
I found solution. Need to call save method instead update.
In my case update will be looks like
import {plainToClass} from 'class-transformer';
public async update(req: Request): Promise<Dto> {
const found = await this.repo.findOneOrFail(req.params.id, {
relations: ['owners', 'electricMeter'],
});
const dto = {
...found,
...req.body,
owners: this.formatJoinData(req.body.owners) //[1,2,3] => [{id:1},{id:2},{id:3}]
updatedBy: this.getUser(req),
updatedDate: Date.now(),
};
return this.repo.save(plainToClass(Entity, dto));
}
This code can be improved, but think that main idea is clear.
https://typeorm.io/#/many-to-many-relations documentation doesn't say more than use JoinTable decorator, and we don't know what you havr in your request, but it looks like you're passing wrong values. These fields are virtual, at the end with m2m relationship third table is created to handle relationship.

Typeorm how to get relations of relations

I am getting the Object ChatRoomEntity with entitymanager.findOne method. The ChatRoomEntity has the variable messages which is a OneToMany - ManyToOne Relation. I have no problems to select that but how do I get the user which sent the message. Its a variable on MessageEntity with a OneToMany Relation.
So basically I want to select a room and all messages of it. But all messages should also have their values on fromUser.
I select the room like this:
this.entityManager.findOne(ChatRoomEntity, {where: {id: roomToJoin.id}, relations: ['activeUsers', 'messages']}).then(roomEntity => {
// some code
}
Here my entities:
UserEntity
#Entity()
export class UserEntity {
#PrimaryGeneratedColumn()
id: number;
#CreateDateColumn()
registrationDate: Date;
#ManyToMany(type => ChatRoomEntity, room => room.activeUsers, {cascade: true})
#JoinTable()
activeChatRooms: ChatRoomEntity[];
#OneToMany(type => ChatRoomMessageEntity, msg => msg.fromUser)
chatRoomMessages: ChatRoomMessageEntity[];
}
ChatRoomEntity
#Entity()
export class ChatRoomEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar', {nullable: true})
title: string;
#OneToMany(type => ChatRoomMessageEntity, chatrmsg => chatrmsg.chatRoom)
messages: ChatRoomMessageEntity[];
#ManyToMany(type => UserEntity, user => user.activeChatRooms)
activeUsers: UserEntity[];
}
ChatRoomMessageEntity
#Entity()
export class ChatRoomMessageEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar', {nullable: true})
message: string;
#CreateDateColumn()
creationDate: Date;
#ManyToOne(type => UserEntity, user => user.chatRoomMessages)
fromUser: UserEntity;
#ManyToOne(type => ChatRoomEntity, chatRoom => chatRoom.messages)
chatRoom: ChatRoomEntity;
}
We can load sub-relations by using 'relation.subrelation' within the relations array itself like this:
relations: ['relation1', 'relation2', 'relation2.subrelation1']
So for your case, instead of using join you can simply do something like this:
this.entityManager.findOne(ChatRoomEntity, {
where: {id: roomToJoin.id},
relations: ['activeUsers', 'messages', 'messages.fromUser'],
}).then(roomEntity => {
...
This is specified here: https://github.com/typeorm/typeorm/blob/master/docs/find-options.md#basic-options
Using QueryBuilder
await getRepository(UserEntity)
.createQueryBuilder('user')
.leftJoinAndSelect('user.profile', 'profile')
.leftJoinAndSelect('profile.images', 'images')
.getMany()
Using FindOptions
await getRepository(UserEntity).find({
relations: ['profile', 'profile.images']
})
Update v0.4.0
Typeorm is ditching findOne, you should use findOneBy for the queries without relations, and if you want relations, simply use find.
const users = await userRepository.find({
where: { /* conditions */ },
relations: { /* relations */ }
})
// users[0] if you want a first row
here is the #gradii/fedaco orm how to implement this feature.
use can use multi relations by method with
export class User extends Model {
#HasOneColumn({related: Profile})
profile;
}
export class Profile extends Model {
#HasOneColumn({related: Image})
image;
}
export class Image extends Model {
name;
}
eager load
User.createQuery().with('profile.image').get()
lazy load
const user = await User.createQuery().first();
const image await (await user.profile).image