Cascade delete in Typeorm for one-to-one - typeorm

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

Related

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.

Is there a way to flatten the fields returned by embeddedEntities in the parent entity?

If you have models set up as such:
#Entity()
class parentEntity {
#Column()
public id;
#Column(() => Name, { prefix: false })
name: Name;
}
export default class Name {
#Column()
firstName;
#Column()
lastName();
}
And you run
const test = await manager.findOne(ParentEntity, { id: 'stuffs' });
console.log(test);
You'll get the following result:
ParentEntity {
id: stuffs,
name: {
firstName: 'hello'
lastName: 'friends'
}
}
But I'd like for there to be a way to have the found entity assume this format:
ParentEntity {
id: stuffs,
firstName: 'hello'
lastName: 'friends'
}
Is there any way to achieve this with typeORM as it currently is? Thanks a ton!

Error: Object metadata not found after adding many-to-many relationships

I need to have additional properties for many-to-many relationships, I created a new entity myself as shown in the documentation.
https://github.com/typeorm/typeorm/blob/master/docs/many-to-many-relations.md#many-to-many-relations-with-custom-properties
It's user entity.
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { ShareTask } from './../../share-taks/entity/share-taks.entity';
#Entity('users')
export class User {
#PrimaryGeneratedColumn()
public id: number;
#Column({ type: 'varchar', nullable: false })
public email: string;
#Column({ type: 'varchar', nullable: false })
public password: string;
#OneToMany(() => ShareTask, (shareTask: ShareTask) => shareTask.user)
public shareTask: ShareTask[];
}
It's task entity.
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { ShareTaks } from './../../share-taks/entity/share-taks.entity';
#Entity('tasks')
export class Taks {
#PrimaryGeneratedColumn()
public id: number;
#Column({ type: 'varchar', nullable: false, length: 50 })
public title: string;
#OneToMany(() => ShareTaks, (shareTaks: ShareTaks) => shareTaks.taks)
public shareTaks: ShareTaks[];
}
It's ormconfig.json.
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "7047",
"database": "db",
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": true
}
It's ShareTaks entity.
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './../../users/entity/user.entity';
import { Taks } from './../../taks/entity/taks.entity';
#Entity('shareTasks')
export class shareTask {
#PrimaryGeneratedColumn()
public id: number;
#Column({ type: 'integer', nullable: false })
public taskId: number;
#Column({ type: 'integer', nullable: false })
public userId: number;
#ManyToOne(() => (task: Task) => task.shareTask, { cascade: ['insert', 'update', 'remove'] })
public task: Task;
#ManyToOne(() => (user: User) => user.shareTask, { cascade: ['insert', 'update', 'remove'] })
public user: User;
}
Before I made a many-to-many relationship, everything worked well. When I added a many-to-many relationship, I get an error.
Error: Entity metadata for ShareTask#task was not found. Check if you specified a correct entity object and if it's connected in the connection options.
Because of what I get an error, I can’t understand.
I found some answers to my question, but they did not help me.
Entity metadata for Role#users was not found
https://github.com/typeorm/typeorm/issues/420
Likely, the error is caused by the path you provided in the entities in ormconfig file. Try changing it to a relative path from the current directory, something like
"entities": [__dirname + "/../**/*.entity{.ts,.js}"]
I resolved this by exporting from an index, and then importing from that index in both my User and Role file. When I was importing directly from the class files I was getting the this error.
Error during server startup TypeORMError: Entity metadata for User#role_id was not found. Check if you specified a correct entity object and if it's connected in the connection options.
My guess is that the strict equals (===) is colliding with a cache and thinking that the different relative import paths refer to different classes.
src/entities/index.ts:
import Team from "./Team";
import Role from "./Role";
import User from "./User";
export { Team, Role, User };
DataService.ts (where the datasources are defined)
import { Role, Team, User } from "../entities";
...
entities: [Role, Team],
...
src/entities/User.ts
import { Team, Role } from "../entities";
...
#Column()
#IsNotEmpty()
#ManyToOne(() => Role, (role) => role.id)
role_id: number;

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