TypeORM upsert entities with OneToMany relationships - typeorm

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

Related

TypeORM QueryBuilder Insert Into Table With Foreign Key Constraint

I have two tables - organizations and departments. The departments table has a foreign key constraint with organizations on the organizationId column. I obviously have lots of other foreign key relationships for these two tables. This is just on use case.
Doing a select statement on both tables works fine:
import "reflect-metadata";
import {createConnection} from "typeorm";
import {getConnection} from "typeorm";
import {Departments} from "./entity/Departments"
createConnection().then(async connection => {
//get total estimated hours by month
const departments = await getConnection()
.createQueryBuilder( "Departments", "d")
.innerJoinAndSelect("organizations", "o", "d.organizationId = o.organizationId")
.select("organizationName, departmentName ")
.where("d.organizationId = :organizationId", {organizationId: 2})
.getRawMany();
// .getSql();
console.log("Departments: ", departments);
}).catch(error => console.log(error));
When I try to do an insert into Departments using QueryBuilder, organizationId isn't recognized
as a valid column:
import "reflect-metadata";
import {createConnection} from "typeorm";
import {getConnection} from "typeorm";
import {Departments} from "./entity/Departments"
createConnection().then(async connection => {
//get total estimated hours by month
await getConnection()
.createQueryBuilder()
.insert()
.into(Departments)
.values([
{ departmentName: "Test Product", organizationId: 2}
])
.execute();
// .getSql();
}).catch(error => console.log(error));
Here's the entity code for both organizations and departments:
Organizations.ts -
import {
Column,
Entity,
Index,
OneToMany,
PrimaryGeneratedColumn,
} from "typeorm";
import { Departments } from "./Departments";
import { Groups } from "./Groups";
import { IntegrationDetails } from "./IntegrationDetails";
import { Jobcodes } from "./Jobcodes";
import { JobMaster } from "./JobMaster";
import { Locations } from "./Locations";
import { Phases } from "./Phases";
import { RoadmapActualCostDetails } from "./RoadmapActualCostDetails";
import { RoadmapEstimatedCostDetails } from "./RoadmapEstimatedCostDetails";
import { RoadmapEstimates } from "./RoadmapEstimates";
import { RoadmapTemplates } from "./RoadmapTemplates";
import { Strategies } from "./Strategies";
import { Teammates } from "./Teammates";
#Index("organizations_organizationId_index", ["organizationId"], {})
#Index("organizations_pk", ["organizationId"], { unique: true })
#Entity("organizations", { schema: "dbo" })
export class Organizations {
#PrimaryGeneratedColumn({ type: "int", name: "organizationId" })
organizationId: number;
#Column("varchar", { name: "organizationName", length: 100 })
organizationName: string;
#Column("varchar", { name: "orgShortName", length: 50 })
orgShortName: string;
#OneToMany(() => Departments, (departments) => departments.organization)
departments: Departments[];
#OneToMany(() => Groups, (groups) => groups.organization)
groups: Groups[];
#OneToMany(
() => IntegrationDetails,
(integrationDetails) => integrationDetails.organization
)
integrationDetails: IntegrationDetails[];
#OneToMany(() => Jobcodes, (jobcodes) => jobcodes.organization)
jobcodes: Jobcodes[];
#OneToMany(() => JobMaster, (jobMaster) => jobMaster.organization)
jobMasters: JobMaster[];
#OneToMany(() => Locations, (locations) => locations.organization)
locations: Locations[];
#OneToMany(() => Phases, (phases) => phases.organization)
phases: Phases[];
#OneToMany(
() => RoadmapActualCostDetails,
(roadmapActualCostDetails) => roadmapActualCostDetails.organization
)
roadmapActualCostDetails: RoadmapActualCostDetails[];
#OneToMany(
() => RoadmapEstimatedCostDetails,
(roadmapEstimatedCostDetails) => roadmapEstimatedCostDetails.organization
)
roadmapEstimatedCostDetails: RoadmapEstimatedCostDetails[];
#OneToMany(
() => RoadmapEstimates,
(roadmapEstimates) => roadmapEstimates.organization
)
roadmapEstimates: RoadmapEstimates[];
#OneToMany(
() => RoadmapTemplates,
(roadmapTemplates) => roadmapTemplates.organization
)
roadmapTemplates: RoadmapTemplates[];
#OneToMany(() => Strategies, (strategies) => strategies.organization)
strategies: Strategies[];
#OneToMany(() => Teammates, (teammates) => teammates.organization)
teammates: Teammates[];
}
Departments.ts
export class Departments {
#PrimaryGeneratedColumn({ type: "int", name: "departmentId" })
departmentId: number;
#Column("nvarchar", { name: "departmentName", length: 100 })
departmentName: string;
#Column("int", { name: "ownerId", nullable: true })
ownerId: number | null;
#ManyToOne(() => Organizations, (organizations) => organizations.departments)
#JoinColumn([
{ name: "organizationId", referencedColumnName: "organizationId" },
])
organization: Organizations;
#ManyToOne(() => Departments, (departments) => departments.departments)
#JoinColumn([{ name: "parentId", referencedColumnName: "departmentId" }])
parent: Departments;
#OneToMany(() => Departments, (departments) => departments.parent)
departments: Departments[];
#OneToMany(() => Products, (products) => products.department)
products: Products[];
#ManyToMany(() => Programs, (programs) => programs.departments)
#JoinTable({
name: "programs_departments",
joinColumns: [
{ name: "departmentId", referencedColumnName: "departmentId" },
],
inverseJoinColumns: [
{ name: "programId", referencedColumnName: "programId" },
],
schema: "dbo",
})
programs: Programs[];
#OneToMany(() => Roadmaps, (roadmaps) => roadmaps.department)
roadmaps: Roadmaps[];
}
Never mind - I figured it out. Even though the ManyToOne relationships are correctly defined in the entity files, you still have to add a #Column reference.
#Column("int", { name: "organizationId" })
organizationId: number;
Is related to this - Typeorm insert with relationId

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;

class-transformer: serialize typeorm manytoone relation

I have this class:
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from "typeorm";
import {Company} from "./company.entity";
import {classToPlain, Expose, Transform, Type} from 'class-transformer';
#Entity()
export class Space {
#PrimaryGeneratedColumn('uuid')
id?: string;
#ManyToOne(() => Company)
#Expose()
#Type(() => Company)
#Transform(async value => {
const res = await value;
console.log(res);
return res;
})
company!: Promise<Company>;
#Column()
name!: string;
}
and this for the Company:
#Entity()
export class Company {
#PrimaryGeneratedColumn('uuid')
id?: string;
#Column()
name!: string;
#OneToMany(() => Space, space => space.company, {
cascade: true
})
spaces!: Promise<Space[]>;
}
For some reason, the Company always comes back as an empty object within the space object like this:
[
{
"id": "266F2B95-69AE-EA11-96D2-28187800655A",
"name": "Main",
"desks": 2,
"company": {}
}
]
even though the console.log spits out
Company {
id: '09A8FB3E-C5AB-EA11-96D2-28187800655A',
name: 'Name' }
what am I doing wrong here?
Class-transformer calls the transform functions synchronously. You have to use an eager relation:
#ManyToOne(() => Company, { eager: true })
company!: Promise<Company>;
or load the property afterwards like:
export class Space {
...
loadedCompany: Company
...
}
...
for (const space of spaces) {
space.loadedCompany = await space.company
}

Typeorm BaseEntity create function: how to deeply create an entity?

I have a Patient entity:
#Entity()
#ObjectType()
#InputType('PatientInput')
export class Patient extends BaseEntity {
#PrimaryGeneratedColumn()
#Field(type => Int, { nullable: true })
id: number
#Column({ length: 500 })
#Field({ nullable: true })
firstName?: string
#Column({ length: 500 })
#Field({ nullable: true })
lastName?: string
#Field(type => MedicalCondition, { nullable: true })
#OneToMany(type => MedicalCondition, medicalCondition => medicalCondition.patient, { cascade: true })
medicalConditions?: MedicalCondition[]
}
And a MedicalCondition entity:
#ObjectType()
#Entity()
#InputType('medicalCondition')
export class MedicalCondition extends BaseEntity {
#Field({nullable: true})
#PrimaryGeneratedColumn()
id: number
#Field(type => Int, { nullable: true })
#RelationId((medicalCondition: MedicalCondition) => medicalCondition.patient)
#Column()
patientId?: number
#Field(type => Patient, { nullable: true })
#ManyToOne(type => Patient, patient => patient.medicalConditions, {
onDelete: 'CASCADE',
})
#JoinColumn()
patient?: Patient
#Field()
#Column()
name: string
#Field({nullable: true})
#Column()
startDate?: Date
}
When trying to create an instance of patient using the BaseEntity create function, the patient is created but in the medical conditions array only containes the last element and all the rest disappear even if there were many elements.
#Mutation(returns => Patient)
async create(#Args('patient') newPatientInput: Patient, #CurrentUser() user: User, #Useragent() useragent): Promise<Patient> {
const newPatient: Patient = Patient.create(newPatientInput)
const event = createEvent({ useragent, actionType: 'add_patient', userId: user.id })
const p = await this.patientService.create(newPatient, event)
return p
}
create = async (patient: Patient, event: EventLog): Promise<Patient> => {
const isExist = await this.isPatientExist({ firstName: patient.firstName, lastName: patient.lastName, birthDate: patient.birthDate })
if (isExist > 0) {
throw new Error('Patient already exist')
} else {
const transactionResult = runTransaction(async (em) => {
const eventLog = EventLog.create(event)
await em.save(eventLog)
return await em.save(patient)
})
return transactionResult
}
}
I tried to directly save the entity without invoting create():
return await em.save(Patient, patient)
and that was the result of the saving:
medicalConditions:
[ { id: 36,
name: 'heartDisease',
startDate: 2016-07-31T21:00:00.000Z,
kin: 'father',
note: '',
patientId: 26,
createdAt: 2019-11-24T17:11:22.376Z,
updatedAt: 2019-11-24T17:11:22.376Z },
{ id: null,
name: 'previousHeartAttack',
startDate: 2018-04-30T21:00:00.000Z,
kin: 'brother',
note: '' },
{ id: null,
name: 'highBloodPressure',
startDate: 2018-03-31T21:00:00.000Z,
kin: 'sister',
note: '' } ],
Tried google for it and didn't find any known issue.
So the willing result would be to create an entity deeply, is that a possible behavior?

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