Update parent entity when updating children on cascade - typeorm

I am looking for a way to update parents entities when updating children entity using cascade on a OneToMany relationship.
Entities
#Entity()
export class Activity {
#PrimaryGeneratedColumn()
id: number;
#CreateDateColumn()
createAt: Date;
#UpdateDateColumn()
updatedAt: Date;
#OneToMany(
type => ActivityTranslation,
activity_translation => activity_translation.activity,
{
cascade: true
}
)
activity_translations: ActivityTranslation[]
}
#Entity()
export class ActivityTranslation {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#CreateDateColumn()
createAt: Date;
#UpdateDateColumn()
updatedAt: Date;
#ManyToOne(
type => Activity,
activity => activity.activity_translations,
{
onUpdate: 'CASCADE'
}
)
activity: Activity
}
Repository
public async updateActivityTranslation(activity_translation: ActivityTranslationModel): Promise<ActivityTranslationModel> {
try {
delete activity_translation.updatedAt;
return this.repository.save(activity_translation);
} catch {
throw new ServerError("Unable to update activity translation")
}
}
Controller
await this.activityTranslationRepository.updateActivityTranslation(activity_translations[i]);
The activity_translation is updated as expected.
I would like to update the updatedAt column of activity entity when saving activity_translation entity.
I tried everything I found about cascade issues on GitHub and StackOverflow.
Thank you for your help.

I know it is not a solution.... but i was able to do it by forcing the parent entity to change by updating one field. For example, create a field being the number of elements related and update it in the updating method.

Related

Cascade delete in Typeorm for one-to-one

I read and searched a lot but did not find any solution to my problem. I read this, this, ...
My database is MySQL. There is no problem with one-to-many and many-to-many. in one-to-one relation
// Student.ts(Parent)
#Entity({ name: "student" })
export class StudentEntity {
#PrimaryGeneratedColumn()
public id: number;
#Column({ nullable: true })
public name: string;
#OneToOne(() => StudentProfile, (profile) => profile.student, { onDelete: "CASCADE", cascade: ['insert', 'update'] })
public profile: StudentProfile
}
// Profile.ts
#Entity({ name: "profile" })
export class StudentProfile {
#PrimaryGeneratedColumn()
id: number
#Column({ nullable: true })
username: string
#OneToOne(() => StudentEntity, (student) => student.profile, { onDelete: "CASCADE" })
public student: StudentEntity
}
Now, With The following code I want to remove the student and their profile:
const student = await this._studentRepository.findOne({ where: { id: 4 } })
await this._studentRepository.delete(student)
The code above does not work. there is another way: I can remove the student and profile individually, I do not want to do this.
any help would be appreciated. Thanks in advance.
As the stackoverflow you included, mentions: You have to delete referencing side to take cascade deletion to take in effect.
I guess you've to delete like:
const student = await this._studentRepository.findOne({ where: { id: 4 } })
const profile = await this._profileRepository.findOne({ where: { id: student.profile } }) // if you don't have an eager relationship
await this._profileRepository.delete(profile)
or in case of an eager relationship between student and profile:
const student = await this._studentRepository.findOne({ where: { id: 4 } })
await this._profileRepository.delete(student.profile) // if you've an eager relationship

Typeorm: joined relation data is not showing in final result

I have a moving table which is in a one-to-many relation with the moving_activities table. moving_activities table records all the activities happening on the moving table.
I'm trying to fetch all data from moving along with the latest activity from the moving_activites table.
My query:
this.repository
.createQueryBuilder('moving')
.leftJoinAndSelect(
(subQuery) => {
return subQuery
.from(MovingActivityEntity, 'ma')
.where('ma.moving_id = :movingId', { movingId: id })
.orderBy('ma.created_at', 'DESC')
.limit(1);
},
'movingActivities',
'movingActivities.moving_id = moving.id'
)
.where('moving.id = :id', { id })
.getOneOrFail();
MovingEnity
#Entity('movings')
export class MovingEntity {
#PrimaryColumn()
readonly id: string = ulid();
#CreateDateColumn()
readonly createdAt: Date;
#UpdateDateColumn()
readonly updatedAt: Date;
#Column({ nullable: true })
historyId?: string;
#Column({ nullable: true })
postId?: string;
// #OneToMany((type) => MovingActivityEntity, (movingActivity) => movingActivity.moving)
movingActivities: MovingActivityEntity;
}
MovingActivityEntity
#Entity('moving_activities')
export class MovingActivityEntity {
#PrimaryColumn()
readonly id: string = ulid();
#CreateDateColumn()
readonly createdAt: Date;
#Column({ nullable: false })
movingId: string;
#ManyToOne((_type) => MovingEntity)
#JoinColumn({ name: 'moving_id' })
moving?: MovingEntity;
#Column({
type: 'enum',
enum: activityTypes,
default: 'undefined',
})
latestActivityType: ActivityType = 'undefined';
constructor(movingId: string) {
this.movingId = movingId;
}
}
when I run the query I only get the movings table data. there is no moving_activities object.
MovingEntity {
id: '01FVV2VPD7SZ87GWR4PQ840PRX',
createdAt: 2022-02-14T03:01:47.049Z,
updatedAt: 2022-04-22T07:20:05.000Z,
historyId: null,
postId: '01FVV2VPA8M12TV39W4861EVZV',
}
I have also tried to map the data in movingActivities with leftJoinAndMapOne with no luck.
If I use execute() in place of getOneOrFail() I get the intended result but execute() return the raw data.
Custom select work only with raw results.
You can use also getRawOne().
The method leftJoinAndSelect say:
LEFT JOINs given subquery and add all selection properties to SELECT..
so not add the object but all properties to the selection. These added properties are not in the model that you are getting with getOne..., but you can return this data with getRawOne().
Maybe leftJoinAndMapOne can be what you looking for.
Try this query:
this.repository
.createQueryBuilder('moving')
.leftJoinAndMapOne('moving.movingActivities',
(subQuery) => {
return subQuery.select()
.from(MovingActivityEntity, 'ma')
.where('ma.moving_id = :movingId', { movingId: id })
.orderBy('ma.created_at', 'DESC')
.limit(1);
},
'movingActivities',
'movingActivities.moving_id = moving.id'
)
.where('moving.id = :id', { id })
.getOneOrFail();
There is also an old question for the same problem: 61275599
Edit:
Checked in typeorm source code, actually in the current version if we use subquery the mapping is skipped. So the only way to get full results from join with sub query is using getRaw methods.

How to fix 'TypeError: relatedEntities.forEach is not a function' from typeorm

I'm setting up a server using TypeORM + PostgreSQL. When saving saving my entity to the entity's repository, I receive the error: TypeError: relatedEntities.forEach is not a function and the entity is not saved to the database.
This seems to only happen when I am using the #OneToMany or #TreeChildren decorators.
Here is my entity class that is causing the problem:
import { ServiceData } from './service-data.entity';
import { ManufacturerData } from './manufacturer-data.entity';
import { Entity, Column, PrimaryGeneratedColumn, TreeChildren } from 'typeorm';
#Entity()
export class Advertisement {
#PrimaryGeneratedColumn()
id: number;
#Column({ nullable: true })
name?: string;
#Column()
gatewayId: string;
#Column()
rssi: number;
#Column({ nullable: true })
mac?: string;
#TreeChildren()
manufacturerData?: ManufacturerData[];
#TreeChildren()
serviceData?: ServiceData;
}
The (abbreviated) error output is:
UnhandledPromiseRejectionWarning: TypeError: relatedEntities.forEach is not a function
at OneToManySubjectBuilder.buildForSubjectRelation (/<project-directory>/src/persistence/subject-builder/OneToManySubjectBuilder.ts:78:25)
Found the problem.
#TreeChildren and #OneToMany always expect an array. I had to change serviceData?: ServiceData; to serviceData?: ServiceData[];
This error can come up when the oneTomany relationships are not properly written in the Entity design and also when you trying to store the relationships in a table.
The first One is, setting the Entity holding many of the other Entity like this
#OneToMany(() => Address, (address) => address.users)
address: Address[];
The other entity that has manyToOne relationship like this,
#ManyToOne(() => Users, (users) => users.address)
users: Users;
If the relationships are all good, then the problem is storing the entity relations to the database.
when creating the user, store the relationships like this
address = await this.addressRepository.findOneOrFail({
where: {
user_id: Number(userId),
},
});
const user = new User();
user.name = name;
user.age = age;
### store the address as an array of objects to the relationship column
users.address = [address]

How to query a entity based on an related entity property in TypeORM?

I have an Entity Transaction and an Entity Integration
#Entity()
export default class Transaction {
#PrimaryGeneratedColumn()
public id_trans?: number;
#OneToOne(type => Integration, i => i.transaction, { nullable: true })
public integration?: Integration;
}
and
#Entity()
export default class Integration {
#PrimaryGeneratedColumn()
public id_cust?: number;
#OneToOne(type => Transaction, t => t.integration)
#JoinColumn({ referencedColumnName: 'id_trans', name: 'int_id_module' })
public transaction?: Transaction;
}
I tried to query the Transaction by using a Integration property as filter.
const id_api = 10;
const transaction = await repository.find({
where: { integration: { int_id_api: id_api} },
relations: ['integration', 'customer'],
});
but it returns the entire table of Transactions, even if the integration.int_id_api is different from id_api property
What am I doing wrong. What should I do to get this query working ?
You need to add a primary key to all your entities. From the doc:
Each entity must have at least one primary key column. This is a
requirement and you can't avoid it. To make a column a primary key,
you need to use #PrimaryColumn decorator.
Assuming you update the Integration entity like so
#Entity()
export default class Integration {
#PrimaryColumn()
public id_inte: number;
#OneToOne(type => Transaction, t => t.integration)
#JoinColumn({ referencedColumnName: 'id_trans', name: 'int_id_module' })
public transaction?: Transaction;
}
You should be able to find transactions like so
const transaction = await repository.find({
where: { integration: { id_inte: some_id } },
relations: ['integration', 'customer'],
});

Use ObjectId from MongoDB with mysql

Is their any possibility to use mongoDB ObjectID system with MySql and typeORM instead of using an incremental ID?
The goal would be to define my entity like this:
#Entity()
export class RolePermission implements IRolePermission {
#ObjectIdColumn() id?: ObjectID;
#Column()
#IsNotEmpty()
roleId: ObjectID;
#Column()
#IsNotEmpty()
permissionId: ObjectID;
}
My entities could therefore have an ID without even being persisted. The ObjectId system would prevent collisions on the unique constraint I'd like to use for this column.
If a system like that can be implemented, is their any performance downside? I remember implementing such a system with PHP, and at the time, I had read this response that made me think that it was ok: Is there a REAL performance difference between INT and VARCHAR primary keys?
It's in fact really simple. You just need to use the ObjectID object from the mongodb package and declare your entities like you would do usually.
First, install mongodb dependencies:
yarn add mongodb
yarn add #types/mongodb
Then, declare your entity. Here an example with a working relationship between a user and an article:
user.entity.ts:
import { Entity, Column, ManyToOne, PrimaryColumn } from 'typeorm';
import { Article } from './article.entity';
import { ObjectID } from 'mongodb';
#Entity()
export class User {
constructor() {
this.id = (new ObjectID()).toString();
}
#PrimaryColumn()
id: string;
#Column({ length: 500 })
username: string = null;
#OneToMany(type => Article, article => article.user)
articles: Article[];
}
article.entity.ts:
import { Entity, Column, ManyToOne, PrimaryColumn } from 'typeorm';
import { User } from './user.entity';
import { ObjectID } from 'mongodb';
#Entity()
export class Article {
constructor() {
this.id = (new ObjectID()).toString();
}
#PrimaryColumn()
id: string;
#Column({ length: 500 })
title: string = null;
#ManyToOne(type => User, user => user.articles, {nullable: true})
user: User;
}
And use it as you would normally do:
const user = new User();
user.username = 'email#adress.com';
const article = new Article();
article.title = 'Mon titre';
article.user = user;
await this.userRepository.save(user);
await this.articleRepository.save(article);

Resources