How to eager load safely deleted relation in TypeORM - typeorm

Is there any way to load the priceEntity in OrderItemEntity by default no matter the price is safety deleted or not? since I don't want to add the withDelete option everywhere in my code.
#Entity()
export class OrderItemEntity extends AbstractEntity {
#ManyToOne(() => PriceEntity, { eager: true })
price: PriceEntity;
}
#Entity()
export class PriceEntity extends AbstractEntity {
#Column({ type: 'decimal', precision: 10, scale: 2 })
unitPrice: number;
}

Related

Return ONLY selected fields within a TypeORM find request

I'm struggling in returning only selected fields in my TypeORM find request.
Assuming the following request
const data = await AppDataSource.manager.find(User, {
select: {
id: true,
hash: true,
firstname: true,
lastname: false,
},
take: 10, // Just here to shrink dataset
});
The script works pretty well excepted that it return every field of my model, with default value initialized.
[
User {
prefix: 'usr',
hash: 'usr_835b0ad2-XXXXXX',
email: undefined,
accountValidated: false,
role: 'free',
myKeyOne: true,
myKeyTwo: false,
gender: 'unspecified',
lastConnexion: 2023-01-19T10:11:02.733Z,
pendingDeletion: false,
deletionDate: undefined,
firstname: 'Clément',
lastname: undefined,
password: undefined,
facebookId: undefined,
googleId: undefined,
id: 158
},
...
]
Of course, it's not usable as it, because I have extensive relations, and thus the payload would be extremely heavy.
Are you aware of a method / a way to remove all unnecessary fields ?
i.e. I'm expecting
[
User {
id: 124,
hash: 'urs_XXXX',
firstname: 'Clément',
},
...
]
In older versions of typeorm I think you need to select with an array of strings, try:
select: ["id", "hash", "firstname"],
See this older version of the docs: https://github.com/typeorm/typeorm/blob/bc60dd559ba42af083ddea17f01205c78c83c7e0/docs/find-options.md
After hours of researches I've finally found out why it behaved like this.
TypeORM relies on class definitions and typescript so...
if you have typescript default values OR if you have rewrite your constructor, all the "default" properties are injected.
Assuming a User model
❌ You should not do
#Entity({ name: 'users' })
class User {
#Column()
firstname?: string;
#Column({ nullable: true })
lastname?: string;
#Column({ unique: true, nullable: false })
email!: string;
#Column({ name: 'account_validated', nullable: false})
accountValidated?: boolean = false
//Your other fields...
}
✅ You should do
#Entity({ name: 'users' })
class User {
#Column()
firstname?: string;
#Column({ nullable: true })
lastname?: string;
#Column({ unique: true, nullable: false })
email!: string;
// Use default argument of the decorator
#Column({ name: 'account_validated', nullable: false, default: false})
accountValidated?: boolean
//Your other fields...
}
And if you need in some way to init a default, then create a public static method which return the Entity instead of using the constructor.
#Entity({ name: 'users' })
class User {
//...fields
public static init(...params): User {
let _user = new User()
//...populate your object
return _user
}
}

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

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.

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: 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