Typeorm: joined relation data is not showing in final result - typeorm

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.

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

How to find all entities with a certain value in its array element?

My entity contains an array element with enum type.
I have to find all entities with a certain enum value contained in its array element.
Like this.
enum AccessoryType {
Patio = 'PATIO',
DriveWay = 'DRIVE_WAY',
...
}
enum MaterialType {
Granite = 'GRANITE',
BlueStone = 'BLUE_STONE',
NaturalStone = 'NATURAL_STONE',
...
}
#Injectable()
class IdeaEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
url: string;
#Column({ type: 'enum', enum: AccessoryType })
accessoryType: AccessoryType;
#ApiProperty({ enum: MaterialType, isArray: true })
#Column({
type: 'enum',
enum: MaterialType,
array: true,
})
materialTypes: MaterialType[];
}
function myQueryFunction(accessoryType: AccessoryType, materialTypes: MaterialType[], skip: number, take: number): Promise<IdeaEntity[]> {
...
return ideaRepository.find({
where: {
accessoryType: accessoryType,
materialTypes: {
contains: materialTypes
}
}, skip, take
});
...
}
Or at least (with single value provided):
where: {
...
materialTypes: {
contains: MaterialType.Granite
}
...
}
Is there a way to use typeorm function to achieve this?
Have your tried this way: https://github.com/typeorm/typeorm/issues/1401
But I recommend you to create another MaterialTypesEntity with relations in each tables (with this approach there is no problem to find entities)

Update parent entity when updating children on cascade

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.

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'],
});

Is it possible to 'protect' a property and exclude it from select statements

I'd like to protect certain properties on the data-layer level. For example I'd like to protect the password hash I store in the database for a user, so that it doesn't show up in arbitrary select-statements.
This way only when it's explicitly requested in a select property, property2 statement.
I think a more accurate answer would be to set select: false on column options:
#Column({ select: false })
password: string;
And explicitly select the column like this:
const user = await getRepository(User)
.createQueryBuilder()
.addSelect('password')
.getOne()
TypeORM goes well with routing-controllers so you should use it, behind the scenes it uses class-transformer to serialize and deserialize your data. So you can use the #Exclude decorator from that library to prevent certain properties being sent down to the clients.
It also uses the class-validator library to validate the data when specifying it as the type in the controller functions. These are powerful toys. Here is a small example of how you can leverage both:
import { Entity, Column, PrimaryGeneratedColumn, Index, OneToMany } from "typeorm";
import { Exclude, Expose } from "class-transformer";
import { IsNotEmpty, IsEmail, MinLength, MaxLength, Min, Max, IsNumber, IsString } from "class-validator";
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
#IsNotEmpty()
#IsEmail()
#Index({ unique: true })
email: string;
#Exclude()
#Column()
passwordHash: string;
#Column()
#IsNotEmpty()
#IsString()
firstName: string;
#Column()
#IsNotEmpty()
#IsString()
lastName: string;
#Column({ type: 'integer', default: Gender.NotSpecified })
#IsNumber()
#Min(1)
#Max(3)
gender: Gender;
#Expose()
get admin() {
return this.role == Role.Admin;
}
#Expose()
get stylist() {
return this.role == Role.Stylist;
}
}
If you use an another server-side library you can still take advantage of class-transformer and class-validator. You just need to call the validate function manually in your routes, for example for restify you can write:
import {validate } from "class-validator";
import {plainToClass} from "class-transformer";
// ... more code
server.post('/hello', function create(req, res, next) {
let bodyJSON = parseBodyTheWayYouWant(req.body);
let post = plainToClass(bodyJSON);
validate(post)
return next();
});
You can use delete
Exemple Find All users
async findUsers(){
const users:User[] = await userRepository.find();
return users.map(user => {
delete user.password;
delete user.salt;
return user;
}) ;
}
Exemple Find User By Id
async findUserById(id){
const user:User = await userRepository.findOne(id);
delete user.password;
return user;
}
Here is solution - https://github.com/typeorm/typeorm/issues/535
The simplest solution is to exclude field(s) during query.

Resources