How to map joined table column to an entity's field in TypeORM - postgresql

There are two entities as follow:
// user.entity.ts
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
#RelationId((user: User) => user.group)
groupId: number;
#Column()
fullName: string;
#Column()
email: string;
#Column()
passwordHash: string;
#ManyToOne(type => Group, group => group.users)
#JoinColumn()
group: Group;
isOwner: boolean;
}
// group.entity.ts
#Entity()
export class Group extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column({ default: false })
isOwner: boolean;
#OneToMany(type => User, user => user.group)
users: User[];
}
I'd like to map the isOwner value of Group to isOwner of User
I tried:
async findOneById(id: number): Promise<User> {
return await User.createQueryBuilder('user')
.leftJoinAndMapOne('user.isOwner', 'user.group', 'group')
.select(['user.id', 'user.fullName', 'user.email', 'group.isOwner'])
.getOne();
}
the result was:
It is possible to achieve that by using #AfterLoad() or with JS or with raw query.
BUT
Is it possible to implement that using the orm on the query level?

Something like that could be as a solution:
findOneById(id: number): Promise<User> {
return User.createQueryBuilder('user')
.leftJoinAndMapOne('user.isOwner', 'user.group', 'group')
.select(['user.id', 'user.fullName', 'user.email', 'group.isOwner AS user.isOwner']) // or probably 'group.isOwner AS user_isOwner'
.getOne();
}
And you could look at this answer, hope it would be helpful

Related

Is there a way to batch insert many to many relationship values into junction table using typeorm?

I have 2 Entities Group and Scorecard. I have setup a many to many relationship between them using a junction table. Here is how my entities look.
Group:
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Field()
#Column()
name!: string;
#OneToMany(() => Scorecard_Group, (sg) => sg.group)
scorecardConnection: Promise<Scorecard_Group[]>;
Scorecard:
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Field()
#Column()
name!: string;
#OneToMany(() => Scorecard_Group, (sg) => sg.scorecard)
groupConnection: Promise<Scorecard_Group[]>;
Junction Table Scorecard_Group:
export class Scorecard_Group extends BaseEntity {
#PrimaryColumn()
groupId: number;
#PrimaryColumn()
scorecardId: number;
#ManyToOne(() => Scorecard, (sc) => sc.groupConnection, {
primary: true,
})
#JoinColumn({ name: "scorecardId" })
scorecard: Promise<Scorecard>;
#ManyToOne(() => Group, (group) => group.scorecardConnection, {
primary: true,
})
#JoinColumn({ name: "groupId" })
group: Promise<Group>;
}
Upon creating a new scorecard a user can select for which groups this scorecard should be long to. So it is innefficient to add the connections on for each group with a single mutation. Currently this is how i am adding the relationship.
#Mutation(() => Boolean)
async addScorecardGroup(
#Arg("scorecardId", () => Int) scorecardId: number,
#Arg("groupId", () => Int) groupId: number
) {
await Scorecard_Group.create({ scorecardId, groupId }).save();
return true;
}
Is there a way in TypeORM where I can add these connection more efficiently in less mutations and without using loops? I checked this https://github.com/typeorm/typeorm/issues/1025 but i believe it applies to many to many relationship that are setup automatically by typeorm.

Find rows using foreign key in TypeORM

I have an OneToMany relation from User to Message.
When I insert register a user with a message, it adds the user to the User table, and the message to the Message table with a userId pointing to the user's id.
This is done automatically using the following setup.
User entity:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
email: string;
#JoinTable()
#OneToMany((type) => Message, (message) => message.user, {
cascade: true,
})
messages: Message[];
}
Message entity:
#Entity()
export class Message {
#PrimaryGeneratedColumn()
id: number;
#Column()
text: string;
#ManyToOne((type) => User, (user) => user.messages, { eager: true })
user: User[];
}
If I want to find all messages from a user via userId with:
const existing_msgs = await this.messageRepository.find({
where: { userId: user.id },
});
It tells me that it cannot find the userId column, which is understandable as I did not specifically include userId to the Message entity.
But how would we query it in this case?
As mentionned in This post, you have to add the decorator #JoinColumn after your manytoone. This will join the column and you will be able to perform the query you want with :
const existing_msgs = await this.messageRepository.find({
where: { user: { id: user.id }},
});
Note that this will work only on primary column like id.
And your message table will be :
#Entity()
export class Message {
#PrimaryGeneratedColumn()
id: number;
#Column()
text: string;
#ManyToOne((type) => User, (user) => user.messages, { eager: true })
#JoinColumn() // <-- Add this
user: User[];
}
I was able to do it with the following querybuilder.
const msg_arr = await this.userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.messages', 'messages')
.where('user.id = :userId', { userId: user.id })
.andWhere('messages.text LIKE :text', { text: message })
.select('messages.text')
.execute();

Cyclic dependency with Postgres

I have two entities called User and Ad and the relation is 1:M, when I need to create a new Ad, I need to pass the announcer_id together.
Ad.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
JoinColumn,
} from 'typeorm';
import User from './User';
#Entity('ads')
class Ad {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
announcer_id: string;
#ManyToOne(() => User)
#JoinColumn({ name: 'announcer_id' })
announcer: User;
}
export default Ad;
User.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
#Entity('users')
class Ad {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
name: string;
#Column()
email: string;
#Column()
password: string;
#CreateDateColumn()
created_at: Date;
#UpdateDateColumn()
updated_at: Date;
}
export default Ad;
CreateAdService.ts
import { getCustomRepository } from 'typeorm';
import Ad from '../models/Ad';
import AdsRepository from '../repositories/AdsRepository';
interface IRequest {
announcer_id: string;
title: string;
description: string;
price: number;
n_room: number;
n_bathroom: number;
garage: boolean;
}
class CreateAdService {
public async execute({
announcer_id,
title,
description,
price,
n_room,
n_bathroom,
garage,
}: IRequest): Promise<Ad> {
const adsRepository = getCustomRepository(AdsRepository);
const priceNegative = adsRepository.isNegative(price);
if (priceNegative) {
throw new Error("You can't create an announcement with negative price.");
}
const ad = adsRepository.create({
announcer_id,
title,
description,
price,
n_room,
n_bathroom,
garage,
});
await adsRepository.save(ad);
return ad;
}
}
export default CreateAdService;
The Error
{
"error": "Cyclic dependency: \"Ad\""
}

Cannot delete a OneToMany record in TypeORM

I have the following schemas
#Entity()
export class Question extends BaseEntity {
#PrimaryColumn()
messageId: string;
#Column()
authorId: string;
#Column()
question: string;
#Column("varchar", { array: true })
possibleAnswers: string[];
#Column()
isAnonymous: boolean;
#OneToMany(() => Answer, (answer) => answer.question, { eager: true })
answers: Answer[];
get formattedAnswers() {
return this.possibleAnswers
.map((answer, idx) => `${numericEmojis[idx]}: **${answer}**`)
.join("\n");
}
}
#Entity()
#Unique("uc_ids", ["userId", "questionMessageId"])
export class Answer extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
userId: string;
#Column()
answerIndex: number;
#ManyToOne(() => Question, (question) => question.answers)
question: Question;
#Column({ readonly: true })
// #ts-expect-error
private readonly questionMessageId: string;
}
Whenever I try to delete like
const question = await Question.findOne(message.id);
await Question.delete(question);
I get the following error:
err: query: SELECT "Question"."message_id" AS "Question_message_id", "Question"."author_id" AS "Question_author_id", "Question"."question" AS "Question_question", "Question"."possible_answers" AS "Question_possible_answers", "Question"."is_anonymous" AS "Question_is_anonymous", "Question__answers"."id" AS "Question__answers_id", "Question__answers"."user_id" AS "Question__answers_user_id", "Question__answers"."answer_index" AS "Question__answers_answer_index", "Question__answers"."question_message_id" AS "Question__answers_question_message_id" FROM "question" "Question" LEFT JOIN "answer" "Question__answers" ON "Question__answers"."question_message_id"="Question"."message_id" WHERE "Question"."message_id" IN ($1) -- PARAMETERS: ["729340583583285289"]
err: (node:19515) UnhandledPromiseRejectionWarning: EntityColumnNotFound: No entity column "answers" was found.
Originally I was trying to setup a cascade delete so that when I remove a question, the answers are removed as well, I got the same error but even after removing the cascade delete I get the same one, how can I fix this? I am using a Postgres database with the SnakeNamingStrategy
Works for me by adding onDelete:"CASCADE" or onDelete:"SET NULL".
I have the following schemas
#Entity()
export class Question extends BaseEntity {
#PrimaryColumn()
messageId: string;
#Column()
authorId: string;
#Column()
question: string;
#Column("varchar", { array: true })
possibleAnswers: string[];
#Column()
isAnonymous: boolean;
#OneToMany(() => Answer, (answer) => answer.question, { eager: true })
answers: Answer[];
get formattedAnswers() {
return this.possibleAnswers
.map((answer, idx) => `${numericEmojis[idx]}: **${answer}**`)
.join("\n");
}
}
#Entity()
#Unique("uc_ids", ["userId", "questionMessageId"])
export class Answer extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
userId: string;
#Column()
answerIndex: number;
// set onDelete as cascade for automatically delete from parent entity
#ManyToOne(() => Question, (question) => question.answers, { cascade: true, onDelete: "CASCADE" })
question: Question;
#Column({ readonly: true })
// #ts-expect-error
private readonly questionMessageId: string;
}

How can I create a query on nested documents?

I am trying to find a document inside nested collection, but I do not know how should I create query for this. In TypeORM documentation there is only one simple example how to use find/findOne on not nested documents.
I created this query:
const result: BlogEntity = await this.blogRepository.findOne({posts: {_id: article._id}});
but when I am trying to build my project i am getting this error:
error TS2345: Argument of type '{ posts: { _id: ObjectID; }; }' is not assignable to parameter of type 'string | number | Date | ObjectID | FindOneOptions | Partial'.
After this i also tried :
const result: BlogEntity = await this.blogRepository.findOne({posts: {_id: article._id}});
const result: BlogEntity = await this.blogRepository.findOne({'posts._id': article._id});
const result: BlogEntity = await this.blogRepository.findOne({where: {posts: { _id: article._id}}});
const result: BlogEntity = await this.blogRepository.findOne({where: {'posts._id': _id: article._id}});
const result: ArticleEntity = await this.mongoManager.findOne(ArticleEntity, {_id: article._id});
But none of them is working
So the question is how should i correctly create this find query. BlogEntity and ArticleEntity code bellow
BlogEntity
#Entity()
#ObjectType()
export class BlogEntity {
#Field(() => ID)
#ObjectIdColumn()
_id!: ObjectID;
#Field(() => [ArticleEntity])
#Column()
posts!: ArticleEntity[];
#Field(() => [ArticleEntity])
#Column()
projects!: ArticleEntity[];
#Field(() => [ArticleEntity])
#Column()
tutorials!: ArticleEntity[];
}
ArticleEntity
#Entity()
#ObjectType()
export class ArticleEntity {
#Field(() => ID)
#ObjectIdColumn()
_id!: ObjectID;
#Field()
#Column()
title!: string;
#Field(() => [String])
#Column()
tags!: string[];
#Field()
#Column()
release!: Date;
#Field(() => [SectionEntity])
#Column()
sections!: SectionEntity[];
#Field({ nullable: true })
#Column()
tutorial?: string;
#Field({ nullable: true })
#Column()
app?: string;
}
If you need anything else ping me in the comment section.
When you are working with nested object's you need to use (.) operator
await this.blogRepository.findOne({'posts._id': article._id});
So it is impossible to achive this in TypeORM. The only one solution which I see there is to swich to Typegoose or Mongoose.
If you are more interested in this issue you can read more here: Issue: Query an Array of Embedded Documents #2483