Querying a Many to Many relation Junction table - javascript

I have a many to many relation between my products and categories entity like this:
#Entity('products')
export class productsEntity extends BaseEntity{
#PrimaryGeneratedColumn()
id: number;
//..other columns
#ManyToMany( type => categoryEntity, category => category.products, {eager: true,})
#JoinTable({name:"products_categories",
joinColumn: {name: 'productId', referencedColumnName: 'id'},
inverseJoinColumn: {name: 'categoryId', referencedColumnName:'id'}
})
categories: categoryEntity[];
}
Then i have a category entity:
#Entity('categories')
export class categoryEntity extends BaseEntity{
#PrimaryGeneratedColumn()
id: number;
#Column({nullable: false, default: 'all', unique: true})
title: string;
#ManyToMany(()=> productsEntity, product => product.categories)
products: productsEntity[];
}
I am trying to save a new product if the category exists in category Entity. by this code:
async addProduct(productDetails: productsEntity, username, categor: categoryEntity){
const {title, description, price, units} = productDetails;
try{
let newProduct = new productsEntity();
newProduct.title = title;
newProduct.description = description;
newProduct.price = price;
newProduct.units = units;
newProduct.soldBy = username;
newProduct.categories = newProduct?.categories ?? [];
newProduct.categories.push(categor);
categor.products= categor?.products?? [];
categor.products.push(newProduct);
await newProduct.save();
await categor.save();
}
catch(err){
this.logger.error(err.message);
throw new HttpException('Failed adding Product.', HttpStatus.INTERNAL_SERVER_ERROR)
}
}
Now everything seems to be fine except one problem:
productId | categoryId
-----------+------------
8 | 2
24 | 1
This gets updated instead of making a new row for a new product if i add with an existing id. like this
productId | categoryId
-----------+------------
8 | 2
25 | 1
What am i doing wrong?? I cant find any clue.

Related

Get the child type from the parent in typeorm

I have a typeorm entity that uses single table inheritance:
#Entity()
#TableInheritance({ column: { type: "varchar", name: "type" } })
#ObjectType({ isAbstract: false })
export class OrganisationEntity extends BaseEntity {
#Field()
#Column()
name: string;
#Field(() => [UserEntity])
#OneToMany(() => UserEntity, (user) => user.organisation)
users: UserEntity[];
}
and some child entities:
#ChildEntity()
#ObjectType()
class MedicalOrganisation {
}
#ChildEntity()
#ObjectType()
class SoftwareOrganisation {
}
#ChildEntity()
#ObjectType()
class MedicalOrganisation {
}
I'm wondering how I can get the type and the child properties from the parent organisation so that I can do something like:
const organisation = await OrganisationEntity.findOne()
if(organisation.type === "medicalOrganisation"){
...
}
But it seems I'm not allowed to access the type property through the parent. Does anyone know how this can be done?
I'd prefer not to use instanceof because it requires the child entities and is causing circular dependencies.
You've got two options. Either use .createQueryBuilder and get the item using getRawOne and the returned object would contain a field named OrganisationEntity_type which can be used to do the checks. It's value would either be 'MedicalOrganisation', or 'SoftwareOrganisation' and so on.
const orgRepository: Repository<OrganisationEntity> = getRepository(OrganisationEntity);
let organisation = await orgRepository.createQueryBuilder().getRawOne();
// organisation = {
// OrganisationEntity_name: 'name',
// OrganisationEntity_users: [
// {},
// {}
// ],
// OrganisationEntity_type: 'MedicalOrganisation'
// }
Or, you could add the field type in the OrganisationEntity itself like this:
#Entity()
#TableInheritance({ column: { type: "varchar", name: "type" } })
#ObjectType({ isAbstract: false })
export class OrganisationEntity extends BaseEntity {
#Field()
#Column()
name: string;
#Field(() => [UserEntity])
#OneToMany(() => UserEntity, (user) => user.organisation)
users: UserEntity[];
#Column
type: string;
}
and do a straightforward:
let organisation = await OrganisationEntity.findOne();
// organisation =
// MedicalOrganisation {
// name: 'name',
// users: [{}, {}],
// type: 'MedicalOrganisation'
// }
and you get the type field in the object itself.

How to use Typescript Compiler API to resolve type references?

Suppose I have the following interfaces:
interface Person {
name: string;
}
interface Attendee {
person: Person;
id: number;
}
I have already figured out how to use the compiler API to extract string representations of every property's type, e.g.:
{
Attendee: {
person: "Person",
id: "number"
}
}
Here's how I do it: https://github.com/jlkiri/tsx-ray/blob/master/src/index.ts.
It's a combination of typeToString and getTypeOfSymbolAtLocation of the Type Checker.
However I would like to resolve types likes Person to their definition so that I get:
{
Attendee: {
person: {
name: "string";
},
id: "number"
}
}
Is there API I can use to easily do this, or do I have to implement the logic myself?
Check ts-morph. I recently discovered it and it looks promising.
Here is a minimal code that can do what you want:
import {ClassDeclaration, Project} from 'ts-morph';
const project = new Project({);
project.addSourceFilesAtPaths("src/**/*.ts");
const allSourceFiles = project.getSourceFiles();
allSourceFiles.forEach(sourceFile => {
const classes = sourceFile.getClasses();
classes.forEach(cls => {
console.log(`class ${cls.getName()} {`);
const properties = cls.getProperties();
properties.forEach(prop => {
const type = prop.getType();
if(type.isClassOrInterface()) {
const typeSymbol = type.getSymbol();
console.log(` ${prop.getName()} :
${typeSymbol?.getName()} {`);
const clsDeclaration = typeSymbol?.getDeclarations()[0] as ClassDeclaration;
const members = clsDeclaration.getMembers();
members.forEach(m => {
console.log(` ${m.getText()}`);
});
console.log(` }`);
} else {
console.log(` ${prop.getName()} : ${type.getText()}`);
}
});
console.log(`}`);
});
})
For the following two files:
// ./src/property.ts
class Category {
description: string;
id: number;
}
export default Category;
// ./src/product.ts
import Category from './category';
class Product {
name: string;
price: number;
category: Category;
}
export default Product;
you will get the following printout:
class Category {
description : string
id : number
}
class Product {
name : string
price : number
category : Category {
description: string;
id: number;
}
}

fetch all nested resources by resource id on a many to many relationship

I want to create a NestJs API with TypeORM and have two entities, users and groups. A User can join multiple groups and a group can have multiple users.
I created these ORM models for the user
#Entity('User')
export class UserEntity {
#PrimaryGeneratedColumn()
id: number;
// ...
#ManyToMany((type: any) => GroupEntity, (group: GroupEntity) => group.users)
#JoinTable()
groups: GroupEntity[];
}
and for the group
#Entity('Group')
export class GroupEntity {
#PrimaryGeneratedColumn()
id: number;
// ...
#ManyToMany((type: any) => UserEntity, (user: UserEntity) => user.groups)
#JoinTable()
users: UserEntity[];
}
When calling the route GET localhost:3000/users/1/groups I want to return an array of groups the user belongs to. The UsersService executes this
const groups: GroupEntity[] = await this.groupsRepository.find({
where: { userId: 1 },
relations: ['users'],
});
When calling the route GET localhost:3000/groups/1/users I want to return an array of users the group is holding. The GroupsService executes this
const users: UserEntity[] = await this.usersRepository.find({
where: { groupId: 1 },
relations: ['groups'],
});
Unfortunately both endpoints return every nested subresource. It seems the where clause gets ignored. The database creates two cross tables
but I would expect only one cross table because one of them is redundant, no?. Of course this can have technical reasons. What is the correct way to fetch the subresources?
First Problem
In TypeORM when you define #ManyToMany relation you need to use #JoinTable on one (owning) side of relation.
So in this way, it will create only one cross table.
Example
#Entity('User')
export class UserEntity {
#PrimaryGeneratedColumn()
id: number;
// ...
#ManyToMany((type: any) => GroupEntity, (group: GroupEntity) => group.users)
#JoinTable()
groups: GroupEntity[];
}
#Entity('Group')
export class GroupEntity {
#PrimaryGeneratedColumn()
id: number;
// ...
#ManyToMany((type: any) => UserEntity, (user: UserEntity) => user.groups)
users: UserEntity[];
}
It will generate three tables users, groups, and user_groups_group
Second Problem
You can use this query
user = await userRepo.find({
relations: ['groups'],
where: { id: user.id }
});
const groups = user.groups
And as your code suggests that you are using Lazy loading of typeORM, so you can do this
const user = await userRepo.find({
where: { id: someId }
});
const groups = await note.groups

How to save relation in #ManyToMany in typeORM

There are 2 entities named Article and Classification. And the relation of them is #ManyToMany.
Here's my question: How to save the relation?
My code as below:
#Entity()
export class Article {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#CreateDateColumn()
createTime: Date;
#UpdateDateColumn()
updateTime: Date;
#Column({
type: 'text',
})
content: string;
#Column({
default: 0,
})
likeAmount: number;
#Column({
default: 0,
})
commentAmount: number;
}
#Entity()
export class Classification {
#PrimaryGeneratedColumn()
id: number;
#CreateDateColumn()
createTime: Date;
#UpdateDateColumn()
updateTime: Date;
#Column()
name: string;
#ManyToMany(type => Article)
#JoinTable()
articles: Article[];
}
I can save the Article and Classification successful. But I'm not sure how to save the relation of them.
I have tried to save the relation via below code:
async create(dto: ArticleClassificationDto): Promise<any> {
const article = this.repository.save(dto);
article.then(value => {
console.log(value);//console the object article
value.classification.forEach(item => {
const classification = new Classification();
classification.id = item.id;
classification.articles = [];
classification.articles.push(value);
this.classificationService.save(classification);
})
});
console.log(article);
return null;
}
And the post data strcture like that
{
"name":"artile name",
"content":"article content",
"classification":[{
"id":4
},{
"id":3
}]
}
At the beginning, it works.
But when I post the data again, the old record was replaced rather create another record.
What should I do next?
Just look below code please.
async create(dto: ArticleClassificationDto): Promise<any> {
this.repository.save(dto).then(article => {
article.classification.forEach(item => {
this.ClassificationRepository.findOne(
{
// the privous method is get all the articles from databse and push into this array
// relations: ['articles'],
where: { id: item }// now I change the data strcture, just contains id instead of {id}
}
).then(classification => {
// console.log(article);
console.log(classification);
// cmd will show ' UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'push' of undefined' withous below line code. But if I init the array manually,the old record will be replaced again.
// classification.articles = [];
classification.articles.push(article);
this.ClassificationRepository.save(classification);
});
})
})
return null;
}
How to save relations?
Let's assume you have an array of articles and you want to create a relation to a classification entity. You just assign the array to the property articles and save the entity; typeorm will automatically create the relation.
classification.articles = [article1, article2];
await this.classificationRepository.save(classification);
For this to work, the article entities have to be saved already. If you want typeorm to automatically save the article entities, you can set cascade to true.
#ManyToMany(type => Article, article => article.classifications, { cascade: true })
Your example
async create(dto: ArticleClassificationDto): Promise<any> {
let article = await this.repository.create(dto);
article = await this.repository.save(article);
const classifications = await this.classificationRepository.findByIds(article.classification, {relations: ['articles']});
for (const classification of classifications) {
classification.articles.push(article);
}
return this.classificationRepository.save(classifications);
}
in my case i have user and role, 1st you have to initialize your manytomany in your entities :
in user entity :
#ManyToMany((type) => Role, {
cascade: true,
})
#JoinTable({
name: "users_roles",
joinColumn: { name: "userId", referencedColumnName: "id" },
inverseJoinColumn: { name: "roleId" }
})
roles: Role[];
in role entity :
//Many-to-many relation with user
#ManyToMany((type) => User, (user) => user.roles)
users: User[];
in my service i create a new entity from my data then i added role data to my new entity object :
let entity = await this.userRepository.create(data);
let entity2 = {
...entity,
roles: data.selectedRoles,
};
const user = await this.userRepository.save(entity2);
this is the exemple in typeorm website :
const category1 = new Category();
category1.name = "animals";
await connection.manager.save(category1);
const category2 = new Category();
category2.name = "zoo";
await connection.manager.save(category2);
const question = new Question();
question.title = "dogs";
question.text = "who let the dogs out?";
question.categories = [category1, category2];
await connection.manager.save(question);

alias was not found. Maybe you forget to join it

In my nectjs project I'm using TypeORM and I have 2 entities user and post,
and I'm tying to make a relation between them
user.entity.ts
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({ length: 50, unique: true })
name: string;
#OneToMany(type => Post, post => post.user)
posts: Post[];
}
post.entity.ts
#Entity()
export class Post {
#PrimaryGeneratedColumn()
id: number;
#Column({ length: 30 })
title: string;
#ManyToOne(type => User, user => user.posts)
user: User;
}
So I want to join these tables and get post by it's title for specific user
const PostObject = await createQueryBuilder("post")
.leftJoinAndSelect(
"post.user",
"user",
"post.title = :title",
{ title: "title1" }
)
.where("user.id = :id", { id: id })
.getOne();
but when I run the project and execute this function I get this error:
Error: "post" alias was not found. Maybe you forget to join it?
You have to inject the Repository first:
constructor(
#InjectRepository(Post) private readonly postRepository: Repository<Post>
) {
}
Then you can create the QueryBuilder for the Post entity by passing in the alias:
this.postRepository.createQueryBuilder('post')
// ^^^^^^^^^^^^^^^^
.leftJoinAndSelect(
'post.user',
'user',
'post.title = :title',
{ title: 'title1' },
)
.where('user.id = :id', { id })
.getOne();
Can also use getRepository like below.
const PostObject = await getRepository("post").createQueryBuilder("post")
.leftJoinAndSelect(
"post.user",
"user",
"post.title = :title",
{ title: "title1" }
)
.where("user.id = :id", { id: id })
.getOne();

Categories