How to save a Many-To-Many relationship with custom properties with typeORM Query Builder? - typeorm

I need to create a many-to-many relationship between the tables organisation and user, where each user can have a role (admin, follower...) in the organisation.
I have :
Organization
#Entity('organization')
class Organization extends BaseColumns {
#Column({ type: 'character varying', unique: true })
name: string;
#OneToMany(() => OrganizationUser, (organizationUser) => organizationUser.organization, { cascade: true })
organizationUser: OrganizationUser[];
}
User
#Entity('user')
class User extends BaseColumns {
#Column({ type: 'character varying', unique: true })
email: string;
#Column({ type: 'character varying', name: 'password-hash' })
passwordHash: string | null;
#OneToMany(() => OrganizationUser, (organizationUser) => organizationUser.user, { cascade: true })
organizationUser: OrganizationUser[];
}
Role
#Entity('role')
class Role extends BaseColumns {
#Column({ type: 'enum', enum: RoleType, default: RoleType.Contributor })
name: RoleType;
#OneToMany(() => OrganizationUser, (organizationUser: OrganizationUser) => organizationUser.role, { cascade: true })
organizationUser: OrganizationUser[];
}
OrganizationUser
#Entity('organization-user')
class OrganizationUser extends BaseColumns {
#Column({ name: 'user-id' })
userId: string;
#Column({ name: 'organization-id' })
organizationId: string;
#ManyToOne(() => Role, (role: Role) => role.organizationUser, { cascade: true })
role: Role;
#ManyToOne(() => User, (user: User) => user.organizationUser)
user: User;
#ManyToOne(() => Organization, (organization: Organization) => organization.organizationUser)
organization: Organization;
}
How can I add a new insertion in the database with the typeORM Query Builder please ?

Related

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;
}
}

TypeORM relation through third table won't work

Following the TypeORM tutorial (this part - https://typeorm.io/#/many-to-many-relations/many-to-many-relations-with-custom-properties) i created the models in my app:
#Entity({ name: 'organizations'})
export class OrganizationEntity extends AbstractEntity {
#Column()
name: string;
#Column({ nullable: true })
description: string;
#Column({ nullable: true })
logo: string;
#ManyToOne(() => UserEntity, { nullable: true })
mainContactUser: UserEntity;
#OneToOne(() => LicenseEntity, license => license.organization)
license: LicenseEntity;
#OneToMany(() => UserRoleEntity, userRole => userRole.user)
users!: UserRoleEntity[]
}
#Entity({ name: 'users' })
export class UserEntity extends AbstractEntityWithDates {
#Column({ nullable: false })
firstName: string;
#Column({ nullable: false})
lastName: string;
#Column({ nullable: true })
jobTitle: string;
#Column({ unique: true })
email: string;
#Column({ nullable: true })
phone: string;
#Column({ nullable: true })
avatar: string;
#Column({ nullable: true })
oAuthIdentityId: string;
#OneToMany(() => UserRoleEntity, userRole => userRole.user)
userRoles: UserRoleEntity[]
}
and finally:
#Entity({ name: 'user_roles' })
export class UserRoleEntity extends AbstractEntity {
#ManyToOne(() => UserEntity, user => user.userRoles)
user: UserEntity;
#ManyToOne(() => OrganizationEntity, organization => organization.users, { nullable: true })
organization: OrganizationEntity;
#ManyToOne(() => ProjectEntity, { nullable: true })
project: ProjectEntity;
#Column({
type: "enum",
enum: RoleType,
nullable: true
})
role: string;
}
Then in a service i'm performing a query where i want to fetch all organizations with at least related user roles:
const found = await this.organizationRepository
.createQueryBuilder("organization")
.leftJoinAndSelect("organization.users", "users")
.getMany();
No errors but the query generated by TypeORM is as follows:
SELECT "organization"."id" AS "organization_id", "organization"."name" AS "organization_name", "organization"."description" AS "organization_description", "organization"."logo" AS "organization_logo", "organization"."main_contact_user_id" AS "organization_main_contact_user_id", "users"."id" AS "users_id", "users"."role" AS "users_role", "users"."user_id" AS "users_user_id", "users"."organization_id" AS "users_organization_id", "users"."project_id" AS "users_project_id"
FROM "organizations" "organization"
LEFT JOIN "user_roles" "users" ON "users"."user_id"="organization"."id"
This part:
...ON "users"."user_id"="organization"."id"
is obviously incorrect but i can't see why?
Any help would be appreciated.
Your OrganizationEntity has an error in the OneToMany config; You set userRole.user, but it should be userRole.organization, which is the field in that table that links to this field.
#OneToMany(() => UserRoleEntity, userRole => userRole.organization)
users!: UserRoleEntity[]
Also, you should add primary keys to your tables. A safe approach by default is to use a Generated column. You can add it to your 3 tables. For the many-to-many table you could create a compound key, or simply an index with user_id, and company_id, but with tradeoffs - so just use an id on every table to get started.
#PrimaryGeneratedColumn()
id: string;

get items with no child relation or child with certain parameters

how can i get all bookings with no payments or with payments with status pending.
Something like "whereDoesntHave" on Laravel
Thanks!
#Entity()
export class Booking extends BaseEntity{
#Column({type: 'varchar', length: 10})
code: string
#OneToMany(() => Payment, Payment => Payment.booking, { cascade: true })
payments: Payment
}
#Entity()
export class Payment extends BaseEntity {
#ManyToOne(() => Booking, { nullable: false })
booking: Booking
#Column({ type: "float" })
amount: number
#Column()
status: PaymentStatus;
}
First, you should add a foreign key to Payment entity called bookId:
#Entity()
export class Payment extends BaseEntity {
#Column({nullable: true})
bookId:number;
#ManyToOne(() => Booking, { nullable: false })
#JoinColumn({ name: 'bookId' }) // <= and add JoinColumn here
booking: Booking
#Column({ type: "float" })
amount: number
#Column()
status: PaymentStatus;
}
Then, you should get all bookings where bookId on Payment is null:
await Booking.find({
relations:['payments'],
where:[
'payments.status': 'pending',
bookId: null
]
})
[] in where represents OR in the query.

I can't insert related table field

These are my tables:
#Entity('personas')
export class Personas {
#PrimaryGeneratedColumn()
#IsNumber()
id: number;
#Column({type:"varchar",length:100})
#IsNotEmpty()
nombre: string;
#OneToMany(type => Contactos, contactos => contactos.idpersona, {cascade: true})
contactos : Contactos[]
}
#Entity('contactos')
export class Contactos {
#PrimaryGeneratedColumn()
#IsNumber()
id: number;
#Column()
#IsNotEmpty()
idpersona: number;
#ManyToOne(type => Personas, {
})
#JoinColumn({name: "idpersona"})
persona: string;
#Column({type:"varchar",length:100})
nombre: string;
#Column({type:"varchar",length:11})
telefono: string;
}
This is the body of the query to add the records:
{
"nombre":"TestPersona",
"contactos":[{
"nombre":"TestContacto",
"telefono": "123456789"}
]
}
This is the error: [ExceptionsHandler] Field 'idpersona' doesn't have a default value.
It is assumed that the field idPersona in Contacts, should be inserted automatically. What am I doing wrong?. From already thank you very much.
I think you need to add cascade: ['insert'] and possibly add the inverseSide (second param) to #OneToMany:
#Entity({ name: 'persona' })
export class Persona {
#PrimaryGeneratedColumn()
public id: number
#Column()
public nombre: string
#OneToMany(() => Contacto, (contacto) => contacto.idpersona, {
cascade: ['insert']
})
public contactos: Contacto[]
}
#Entity({ name: 'contacto' })
export class Contacto {
#PrimaryGeneratedColumn({
name: 'id',
})
public id: number
#ManyToOne(() => Persona, (persona) => persona.contactos, {
nullable: false,
})
#JoinColumn({ name: 'idpersona' })
#Column()
public idpersona: string
#Column()
public telefono: string
}
Then you can do:
const persona = {
nombre: 'nombre',
contactos: [{
telefono: '123456789',
}]
}
const personaGuardada = await entityManager.getRepository('persona').save([persona])
I leave the way I fix it, in case it helps someone in the future.
In personas changed:
#OneToMany(() => Contacto, (contacto) => contacto.idpersona, {
cascade: ['insert']
})
public contactos: Contacto[]`
with:
#OneToMany(type => Contactos, contactos => contactos.personas,{
cascade : true,
})
public contactos: Contactos[];`
In contactos changed:
#Column()
#IsNotEmpty()
idpersona: number;
#ManyToOne(type => Personas, {
})
#JoinColumn({name: "idpersona"})
persona: string;`
with:
#ManyToOne(() => Personas, personas => personas.id, {nullable: false})
#JoinColumn({name: "idpersona"})
personas: Personas;

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