TypeORM #JoinTable() how to specify custom join columns? - typeorm

this is my sample data model
I have declared the following classes:
#Entity({
name: 'user'
})
export class User {
#Column({ type: 'int4' })
#PrimaryColumn()
userid: number
#Column({name: 'name', type: 'varchar', length: 30})
name: string
#Column({name: 'age', type: 'int2'})
age: number
#ManyToMany(() => Department, (department)=> department.users)
#JoinTable({
name: 'department_user'
})
departments: Department[]
}
#Entity({ name: 'department' })
export class Department {
#Column({ type: 'int2' })
#PrimaryColumn()
departmentid: number
#Column({type: 'varchar', length: 50})
title: string
#Column({type:'text'})
notes: string
#ManyToMany(() => User, (user)=> user.departments)
#JoinTable({ name: 'department_user' })
users: User[]
}
whenever I run the app, it creates the departmentDepartmentId & userUserId columns and not utilize the columns in the corresponding join table. How can I tell typeorm to only use the predefined join column in join table?
Update 2 (as mentioned by #suvantorw)
I recreated the join table with the statement below:
create table department_user(
departmentid integer not null,
userid integer not null);
alter table department_user add constraint fk_dept_dept foreign key (departmentid) references department(departmentid);
alter table department_user add constraint fk_dept_user foreign key (userid) references "user"(userid);
alter table department_user add constraint pk_dept_user primary key (departmentid, userid);
and modified the entities like this:
user
#ManyToMany(() => Department, (department)=> department.users)
#JoinTable({
name: 'department_user',
joinColumn: { name: 'userid' },
inverseJoinColumn: { name: 'departmentid' }
})
departments: Department[]
}
department
#ManyToMany(() => User, (user)=> user.departments)
#JoinTable({
name: 'department_user',
joinColumn: { name: 'departmentid' },
inverseJoinColumn: { referencedColumnName: 'userid' }
})
users: User[]
}
it does run without errors but when it runs the table structure is modified to this
As you can see, ,y foreign key constraints are gone and new ones are created. Any clue what I'm doing wrong here?
Update 3
Finally I modified the classes as below and now the TypeORM accepts the relationships and does not create its own. it was a very painful experience to solve this and documentation about this decorator doesn't say much either.
user
#ManyToMany(() => Department, (department)=> department.users)
#JoinTable({
name: 'department_user',
joinColumn: {
name: 'userid',
foreignKeyConstraintName: 'fk_dept_user'
},
inverseJoinColumn: {
referencedColumnName: 'departmentid',
name: 'departmentid',
foreignKeyConstraintName: 'fk_dept_dept'
}
})
departments: Department[]
}
department
#ManyToMany(() => User, (user)=> user.departments)
#JoinTable({
name: 'department_user',
joinColumn: {
name: 'departmentid',
foreignKeyConstraintName: 'fk_dept_dept'
},
inverseJoinColumn: {
referencedColumnName: 'userid',
name: 'userid',
foreignKeyConstraintName: 'fk_dept_user'
}
})
users: User[]
}

You can specify the join column name and inverse join column name. Something like this should work for you:
#ManyToMany(() => Department, (department)=> department.users)
#JoinTable({
name: 'department_user',
joinColumn: { name: 'userid' },
inverseJoinColumn: { name: 'departmentid' }
})
departments: Department[]

Related

How to join a table using both id and a subjoin property in typeorm?

I have three tables:
Parent {
#PrimaryGeneratedColumn({ type: 'bigint' })
id: string
#OneToMany(() => Child, l => l.parent)
children?: Child[]
}
ChildConfig {
#PrimaryGeneratedColumn({ type: 'bigint' })
id: string
#OneToMany(() => Child, l => l.config)
children: Child[]
#Column()
type: ChildType
}
Child {
#PrimaryGeneratedColumn({ type: 'bigint' })
id: string
#Column()
parentId: string
#Column()
configId: string
#ManyToOne(_ => ChildConfig, config => config.items)
#JoinColumn({ name: 'config_id' })
public config: ChildConfig
#ManyToOne(_ => Parent, item => item.children)
#JoinColumn([{ name: 'parent_id', referencedColumnName: 'id' }, { name: '' }])
public parent?: Parent
}
The Parent table has a one to many relationship with Child.
Child has a many to one relationship with ChildConfig.
I have a requirement that Parent only joins to Child where Child.parentId = Parent.id AND Child.config.type = ChildType.Good.
How do I do this?

TypeORM upsert entities with OneToMany relationships

I have a few entities created on TypeORM and I want to upsert an array of data with all the entities. Here I have the entities:
#Entity({name: "__sales_rep"})
export class SalesRep {
#PrimaryColumn()
ldap: string;
#Column("text")
name: string
#OneToMany(() => ParentCompany, (parent_company) => parent_company.sales_rep, { cascade: ['insert', 'update'] })
parent_companies: ParentCompany[]
}
#Entity({name: "__parent_company"})
export class ParentCompany {
#PrimaryColumn()
id: number;
#Column("text")
name: string
#OneToMany(() => Advertiser, (advertiser) => advertiser.parent_company, { cascade: ['insert', 'update'] })
advertisers: Advertiser[]
#ManyToOne(() => SalesRep, (sales_rep) => sales_rep.parent_companies)
sales_rep: SalesRep
}
#Entity({name: "advertiser"})
export class Advertiser {
#PrimaryColumn()
id: number;
#Column("text")
name: string
#ManyToOne(() => ParentCompany, (parent_company) => parent_company.advertisers)
parent_company: ParentCompany
}
And here is how I am trying to insert the data as cascading the data. I believe the problem is that when I insert two advertisers with the same parent_company for example the constraints of the foreign key aren't allowing me to make the entire insertion.
async function loadData(data) {
console.log("Beggning data insertion");
try{
const insertData = data.rows.map((row) => {
const currentSalesRep ? {
ldap: row.ldap,
name: row.full_name
},
currentParentCompany = {
id: row.parent_company_id,
name: row.parent_company_name,
sales_rep: currentSalesRep
};
return {
id: row.advertiser_id,
name: row.advertiser_name,
parent_company: currentParentCompany
}
})
salesRepRepository
.upsert(insertData, ['id']);
typeorm
}
catch(e){
logger.error(e)
throw e;
}
}

How to expose foreign key column in the entity in TypeORM

I have failed to expose the foreign key column in the entity which I feel weird to not be able to.
If I am not eager loading the relation, at least I should be able to to see the imageId to have some clue of the existence of the relation. so when I do a userRepository.findOne({email: emailaddress}), even I know that I can not eager load the image this way. But at least I could see the imageId.
#Column('datetime', { nullable: true, name: 'last_login' })
lastLogin: string;
#OneToOne(() => UserSetting)
#JoinColumn({ name: 'setting_id' })
setting: UserSetting;
#OneToOne(() => UserImage, { onDelete: 'SET NULL' })
#JoinColumn({ name: 'image_id' })
image: UserImage;
imageUrl: { preview: string, thumbnail: string };
#OneToMany(() => Contact, contact => contact.user)
contacts: Contact[];
#OneToMany(() => Notification, notification => notification.user)
notifications: Notification[];
As you can see, there is no imageId defined. I tried to put it like this. The database just cannot be synced up and it wiped out all my image data too.
#Column({name: 'image_id' })
imageId: string;
#OneToOne(() => UserImage, { onDelete: 'SET NULL' })
#JoinColumn({ name: 'image_id' })
image: UserImage;
Am I missing something simple here?
Your User Entity should look like this:
#Column({ nullable: true })
imageId: string;
#OneToOne(() => UserImage, userImage=> userImage.user, { onDelete: 'SET NULL' })
#JoinColumn({ name: 'image_id' })
image: UserImage;
and in your UserImage Entity:
#OneToOne(() => User, user=> user.image)
user: User;

typeorm - how to add extra field to "many to many" table?

for example:
3 tables
user
user_business_lines_business_line
business_line
those created by typeorm with the declaration in User
#ManyToMany(type => BusinessLine)
#JoinTable()
businessLines: BusinessLine[]
then, how to add columns fields like
#CreateDateColumn({ type: 'timestamp' })
createdAt: Date
#UpdateDateColumn({ type: 'timestamp' })
updatedAt: Date
to user_business_lines_business_line
It is not possible to add custom column in the auto created many-to-many bridge table. So create another table and give one-to-many and many-to-one relationship between them.
for example:
Three tables
User -> Table 1
BusinessLine -> Table 2
UserBusinessLine -> Bridge Table between User table and BusinessLine table
UserBusinessLine table will contain the foreign key of both parent tables and also we can add custom columns into it.
In User Table
#OneToMany(() => UserBusinessLine, (userBusinessLine) => userBusinessLine.user)
public userBusinessLines: UserBusinessLine[];
In BusinessLine Table
#OneToMany(() => UserBusinessLine, (userBusinessLine) => userBusinessLine.businessLine)
public userBusinessLines: UserBusinessLine[];
In UserBusinessLine Table
#ManyToOne(() => User, (user) => user.userBusinessLines)
public user: User;
#ManyToOne(() => BusinessLine, (businessLine) => businessLine.userBusinessLines)
public businessLine: BusinessLine;
// Custom Colums
#CreateDateColumn({ type: 'timestamp' })
createdAt: Date;
#UpdateDateColumn({ type: 'timestamp' })
updatedAt: Date;
So now the custom table has the foreign keys of User table and BusinessLine table. Also the CreateddateColumn and UpdatedDateColumn
You can specify custom-join table for ManyToMany relation
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number
#Column()
name: string
#ManyToMany(type => BusinessLine, businessLine => businessLine.users)
#JoinTable({
name: 'user_business_line',
joinColumn: {
name: 'userId',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'businessLineId',
referencedColumnName: 'id',
},
})
businessLines: BusinessLine[]
}
#Entity('user_business_line')
export class UserBusinessLine {
#CreateDateColumn({ type: 'timestamp' })
createdAt: Date
#UpdateDateColumn({ type: 'timestamp' })
updatedAt: Date
#Column()
#IsNotEmpty()
#PrimaryColumn()
userId: number;
#Column()
#IsNotEmpty()
#PrimaryColumn()
businessLineId: number;
}
#Entity()
export class BusinessLine {
#PrimaryGeneratedColumn()
id: number
#Column()
name: string
#ManyToMany(type => User, user => user.businessLines)
users: User[]
}
If it is just for a createdAt timestamp for example, you could just add an extra column to your user_business_lines_business_line table using migrations like this (I am using Postgres):
ALTER TABLE "user_business_lines_business_line" ADD "createdAt" TIMESTAMP NOT NULL DEFAULT LOCALTIMESTAMP

TypeORM: Insert an entity which has many to many relation with another entity (based on existing records)

I have 2 following entities with Many To Many relationship
User entity (relationship owned by the User entity)
import { Entity, Column, PrimaryGeneratedColumn, UpdateDateColumn, ManyToMany, JoinTable, CreateDateColumn } from 'typeorm';
import { Role } from './role.schema';
#Entity('Users')
export class User {
#PrimaryGeneratedColumn({ name: 'Id' })
id: number;
#Column({
name: 'Email',
length: 100,
unique: true
})
email: string;
#Column({
name: 'FirstName',
length: 30
})
firstName: string;
#Column({
name: 'LastName',
length: 30
})
lastName: string;
#ManyToMany(type => Role, { eager: true })
#JoinTable({
name: 'UserRoles',
joinColumns: [
{ name: 'UserId' }
],
inverseJoinColumns: [
{ name: 'RoleId' }
]
})
roles: Role[];
}
Role entity (with two existing roles: Admin and User)
import { Entity, Column, PrimaryGeneratedColumn, ManyToMany } from 'typeorm';
import { User } from './user.schema';
#Entity("Roles")
export class Role {
#PrimaryGeneratedColumn({ name: 'Id' })
id: number;
#Column({
name: 'Name',
length: 50,
unique: true
})
name: string;
#ManyToMany(type => User, user => user.roles)
users: User[];
}
the design of application is such that Role name is sent in request in the form of an array e.g. ['Admin', 'User']
Now while inserting a User,
currently I first retrieve Role object from database based on the role name array received in request (to assign a desired role),
then assign it to the User object (roles property) and
then finally call save method on User object to insert the record in User table.
Snippet:
createConnection(connectionConfig as ConnectionOptions).then(async connect => {
let userRepo = connect.getRepository(User);
let roleRepo = connect.getRepository(Role);
let roles = ['Admin', 'User'];
let user = userRepo.create();
return roleRepo.createQueryBuilder('role').where('role.name IN (:roleNames)', { roleNames: roles }).getMany().then((roles) => {
user.email = 'test1#test.test';
user.firstName = 'TestFName';
user.lastName = 'TestLName';
user.roles = roles;
return userRepo.save(user)
})
}).catch(error => {
console.log(error);
});
This results in many database calls. It would be great if some one can enlighten me with smarter and more elegant way ( using fluent QueryBuilder to achieve above result)

Resources