Update a many-to-many relationship with TypeORM, Nest JS - many-to-many

I'm having trouble updating an entity that has many-to-many.
Below are the product entities and categories.
#Entity()
class Products {
#PrimaryGeneratedColumn()
public id: number;
#Column()
public title: string;
#Column()
public content: string;
#ManyToMany((type) => Category, (category) => category.products)
#JoinTable()
#Type(() => Category)
categories: Category[];
}
#Entity()
class Category {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#ManyToMany((type) => Products, (products) => products.categories, {
cascade: true,
})
#Type(() => Products)
products: Products[];
}
Update in my service.
async updateProduct(id: number, product: UpdateProductDto) {
await this.service.update(id, product);
const updatedProduct = await this.service.findOne(id, {
relations: ['categories'],
});
if (updatedProduct) {
return updatedProduct;
}
throw new ProductNotFoundException(id);
}
I do not understand what exactly the problem is, since the addition works, the data in the database is there, I send a change request with data similar to how to create it.
{
"content": "test",
"title": "test",
"categories": [
{
"id": 3
}
]
}
Error message
[ExceptionsHandler] column "productsId" of relation "products" does not exist +23003ms
QueryFailedError: column "productsId" of relation "products" does not exist
at new QueryFailedError (...\node_modules\typeorm\error\QueryFailedError.js:12:28)
at PostgresQueryRunner.<anonymous> (...\node_modules\typeorm\driver\postgres\PostgresQueryRunner.js:248:31)
at step (...\node_modules\typeorm\node_modules\tslib\tslib.js:143:27)
at Object.throw (...\node_modules\typeorm\node_modules\tslib\tslib.js:124:57)
at rejected (...\node_modules\typeorm\node_modules\tslib\tslib.js:115:69)
at processTicksAndRejections (internal/process/task_queues.js:93:5)

Related

Problem saving ManyToMany relation in MySQL database via microservices

I have a relationship created between User and Groups, where a group can contain multiple users and a user can be present in more than one group. The relationship was built on the UserEntity and GroupsEntity entities. The problem occurs when the request is made in Postman. The User record is created, but the ids corresponding to the relationship between groups and user are not created in the new table (relationship of the two entities).
Help codes:
UserEntitity.ts
import { AssociateEntity } from 'src/associate/entities/associate.entity';
import { Associate } from 'src/associate/interfaces/associate.interface';
import { GroupsEntity } from 'src/groups/entities/group.entity';
import { Groups } from 'src/groups/interfaces/groups.interface';
import { SubsidiaryEntity } from 'src/subsidiary/entities/subsidiary.entity';
import { Subsidiary } from 'src/subsidiary/interfaces/subsidiary.interface';
import {
Column,
DeleteDateColumn,
Entity,
JoinTable,
ManyToMany,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
#Entity()
export class UserEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
login: string;
#Column()
password: string;
#Column()
email: string;
#Column()
adm: boolean;
#Column()
passwordValidity: string;
#Column()
initials: string;
#Column()
system: string;
#ManyToOne(() => SubsidiaryEntity, (subsidiary) => subsidiary.id)
subsidiary: Subsidiary;
#ManyToMany(() => GroupsEntity, { cascade: true })
#JoinTable()
group: Groups[];
#Column({ default: true })
status: boolean;
#DeleteDateColumn()
deletedAt: Date;
#OneToMany(() => AssociateEntity, (associate) => associate.id)
associate: Associate[];
}
GroupsEntity.ts
import { UserEntity } from 'src/user/entities/user.entity';
import { User } from 'src/user/interfaces/user.interface';
import {
Column,
DeleteDateColumn,
Entity,
ManyToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
#Entity()
export class GroupsEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
description: string;
#Column({ default: true })
status: boolean;
#DeleteDateColumn()
deletedAt: Date;
#ManyToMany(() => UserEntity, (user) => user.group)
user: User;
}
user.service.ts (method create)
async create(user: User) {
try {
const groups: Array<Groups> = await this.groupService.findByIds(
user.groups,
);
//#ts-ignore
user.groups = groups;
const newUser = await this.userRepository.save(user);
return newUser;
} catch (error) {
this.logger.error(`error: ${JSON.stringify(error.message)}`);
throw new RpcException(error.code + error.message);
}
}
The structure of the bank is given by:
Bank entity relationships
The code doesn't report errors, but it doesn't create the relationship with the ids either. I'm using microservices in nestJS, the language is TypeScript and the database used is MySQL.
Join table you have define the join Column and inverseJoinColumn Details. Example
#JoinTable({
name: 'cat_use_cloth',
joinColumn: { name: 'cat_id', referencedColumnName: 'id'},
inverseJoinColumn: { name: 'cloth_id', referencedColumnName: 'id'},
})
Ref: https://medium.com/#rodrigo.tornaciole/nest-js-many-to-many-relationship-using-typeorm-and-crud-ec6ed79274f0

How to retrieve data according relation condition

I have a Product and Image entity with OneToMany relation, where One Product could have many Images.
But the image could be deleted. Instead of removing data from table, what I did is disable it adding a boolean column called 'deleted'. Then if the product image is deleted, the query would return the product without image.
I made the next query that works fine when the product has an image, but I don't know how to ignore the image if it has been deleted.
** my query **
const [ products, count ] = await this.productRepo
.createQueryBuilder('product')
.innerJoinAndSelect('product.images', 'image')
.where([{ name: Like(`%${terms}%`) }, { description: Like(`%${terms}%`) }, { code: Like(terms) }])
.orderBy('product.' + params.orderBy, params.orderMode)
.offset(params.skip)
.limit(params.take)
.getManyAndCount()
return { products, count }
** image entity **
#Entity()
export class Image extends BaseEntity {
#Column()
name: string
#Column({ default: false })
deleted: boolean
#ManyToOne(() => Product, product => product.images) product: Product[];
}
** product entity **
export class Product extends BaseEntity {
#Column({ length: 20 })
code: string
#OneToMany(() => Image, image => image.product) images: Image[]
}
I don't see any reference in the where clause to your new deleted column. I would expect something like { deleted: value } to be in the query.
.where([{ name: Like(`%${terms}%`) }, { description: Like(`%${terms}%`) }, { code: Like(terms) }])

Add a field in a many-to-one relation in NestJS

I am trying to develop a small application to record cooking recipes. To do this, I declared 2 entities with nestJS allowing me to manage the recipes and another to manage the ingredients. I also created a 3rd entity to record the quantities of ingredients needed :
Database diagram
// recipe.entity.js
#Entity()
export class Recipe {
#PrimaryGeneratedColumn()
id: number
#Column('datetime')
createdAt: Date
#Column('datetime')
updatedAt: Date
#Column('varchar', { length: 100 })
title: string;
#Column('varchar', {nullable: true})
image: string;
#OneToMany(type => RecipeIngredients, recipeIngredients => recipeIngredients.recipe)
ingredients: RecipeIngredients[];
}
// ingredient.entity.js
#Entity()
export class Ingredient {
#PrimaryGeneratedColumn()
id: number
#Column('datetime')
createdAt: Date
#Column('datetime')
updatedAt: Date
#Column('varchar', { length: 100 })
name: string;
#Column('varchar', {nullable: true})
image: string;
#OneToMany(type => RecipeIngredients, recipeIngredients => recipeIngredients.ingredient)
recipes: RecipeIngredients[];
}
// recipe_ingredients.entity.js
#Entity()
export class RecipeIngredients {
#PrimaryGeneratedColumn()
id: number
#ManyToOne(type => Recipe, recipe => recipe.ingredients)
recipe: Recipe
#ManyToOne(type => Ingredient)
ingredient: Ingredient
#Column()
quantity: string;
}
First, I would like to be able to retrieve a recipe with the list of necessary ingredients:
const recipe = await this.recipesRepository.createQueryBuilder('recipe')
.where('recipe.id = :recipeId', {recipeId: _id})
.leftJoin('recipe.ingredients', 'recipe_ingredients')
.leftJoin('recipe_ingredients.ingredient', 'ingredient')
.getMany();
But this method returns only my recipe object without the ingredients...
[
{
"id": 1,
"createdAt": "2020-04-30T09:12:22.000Z",
"updatedAt": "2020-04-30T09:12:22.000Z",
"title": "Test",
"image": null
}
]
From there, I'm lost ... How can I get the list of my ingredients (at least the name and quantity fields) directly from my service?
Thank you in advance for your help.
Using leftJoin will able you to join data without its selection. It selects the recipe if it has ingredients, but won't return its ingredients.
As read in TypeORM's documentation :
You can join data without its selection. To do that, use leftJoin or
innerJoin:
const user = await createQueryBuilder("user")
.innerJoin("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.getOne();
This will generate:
SELECT user.* FROM users user
INNER JOIN photos photo ON photo.user = user.id
WHERE user.name = 'Timber'
This will select Timber if he has photos, but won't return his photos.
To select the ingredients try using leftJoinAndSelect instead.

NestJs update Many-To-Many relation with join table

I have two entities - Property and Owner. One Property can have a lot of Owners and Owner can have a lot of Properties. For join use property_owner table. How to update this many-to-many relation using NestJS/TypeORM?
#Entity('property')
export class Property extends EntityModel {
#Column({ length: 255, nullable: false })
name: string;
#ManyToMany(type => Owner, { cascade: true })
#JoinTable({
name: 'property_owner',
joinColumn: { name: 'propertyId', referencedColumnName: 'id'},
inverseJoinColumn: { name: 'ownerId', referencedColumnName: 'id'},
})
owners: Owner[];
}
#Entity('owner')
export class Owner extends EntityModel {
#Column({ length: 255, nullable: false })
name: string;
#ManyToMany(type => Property, { cascade: true })
#JoinTable({
name: 'property_owner',
joinColumn: { name: 'ownerId', referencedColumnName: 'id'},
inverseJoinColumn: { name: 'propertyId', referencedColumnName: 'id'},
})
properties: Property[];
}
Below my service's methods for save and update:
public create(req: Request): Promise<Dto> {
const dto: CreateDto = {
...req.body,
owners: this.formatJoinData(req.body.owners) //[1,2,3] => [{id:1},{id:2},{id:3}]
};
const entity = Object.assign(new Entity(), dto);
return this.repo.save(entity);
}
public update(req: Request): Promise<UpdateResult> {
const dto: EditDto = {
...req.body,
owners: this.formatJoinData(req.body.owners) //[1,2,3] => [{id:1},{id:2},{id:3}]
};
const id = req.params.id;
const entity = Object.assign(new Entity(), dto);
return this.repo.update(id, entity);
}
Saving new Property work fine, but when I try update property I get error
[ExceptionsHandler] column "propertyId" of relation "property" does not exist
Owners data in both cases looks like [{id:1},{id:2},{id:3}]. I think problem in save/update methods results. Save method return to us Entity with id and update method return to us UpdateResult which not contain Entity id. But may be we can transform/additionally define this value somewhere...
I found solution. Need to call save method instead update.
In my case update will be looks like
import {plainToClass} from 'class-transformer';
public async update(req: Request): Promise<Dto> {
const found = await this.repo.findOneOrFail(req.params.id, {
relations: ['owners', 'electricMeter'],
});
const dto = {
...found,
...req.body,
owners: this.formatJoinData(req.body.owners) //[1,2,3] => [{id:1},{id:2},{id:3}]
updatedBy: this.getUser(req),
updatedDate: Date.now(),
};
return this.repo.save(plainToClass(Entity, dto));
}
This code can be improved, but think that main idea is clear.
https://typeorm.io/#/many-to-many-relations documentation doesn't say more than use JoinTable decorator, and we don't know what you havr in your request, but it looks like you're passing wrong values. These fields are virtual, at the end with m2m relationship third table is created to handle relationship.

Typeorm how to get relations of relations

I am getting the Object ChatRoomEntity with entitymanager.findOne method. The ChatRoomEntity has the variable messages which is a OneToMany - ManyToOne Relation. I have no problems to select that but how do I get the user which sent the message. Its a variable on MessageEntity with a OneToMany Relation.
So basically I want to select a room and all messages of it. But all messages should also have their values on fromUser.
I select the room like this:
this.entityManager.findOne(ChatRoomEntity, {where: {id: roomToJoin.id}, relations: ['activeUsers', 'messages']}).then(roomEntity => {
// some code
}
Here my entities:
UserEntity
#Entity()
export class UserEntity {
#PrimaryGeneratedColumn()
id: number;
#CreateDateColumn()
registrationDate: Date;
#ManyToMany(type => ChatRoomEntity, room => room.activeUsers, {cascade: true})
#JoinTable()
activeChatRooms: ChatRoomEntity[];
#OneToMany(type => ChatRoomMessageEntity, msg => msg.fromUser)
chatRoomMessages: ChatRoomMessageEntity[];
}
ChatRoomEntity
#Entity()
export class ChatRoomEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar', {nullable: true})
title: string;
#OneToMany(type => ChatRoomMessageEntity, chatrmsg => chatrmsg.chatRoom)
messages: ChatRoomMessageEntity[];
#ManyToMany(type => UserEntity, user => user.activeChatRooms)
activeUsers: UserEntity[];
}
ChatRoomMessageEntity
#Entity()
export class ChatRoomMessageEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar', {nullable: true})
message: string;
#CreateDateColumn()
creationDate: Date;
#ManyToOne(type => UserEntity, user => user.chatRoomMessages)
fromUser: UserEntity;
#ManyToOne(type => ChatRoomEntity, chatRoom => chatRoom.messages)
chatRoom: ChatRoomEntity;
}
We can load sub-relations by using 'relation.subrelation' within the relations array itself like this:
relations: ['relation1', 'relation2', 'relation2.subrelation1']
So for your case, instead of using join you can simply do something like this:
this.entityManager.findOne(ChatRoomEntity, {
where: {id: roomToJoin.id},
relations: ['activeUsers', 'messages', 'messages.fromUser'],
}).then(roomEntity => {
...
This is specified here: https://github.com/typeorm/typeorm/blob/master/docs/find-options.md#basic-options
Using QueryBuilder
await getRepository(UserEntity)
.createQueryBuilder('user')
.leftJoinAndSelect('user.profile', 'profile')
.leftJoinAndSelect('profile.images', 'images')
.getMany()
Using FindOptions
await getRepository(UserEntity).find({
relations: ['profile', 'profile.images']
})
Update v0.4.0
Typeorm is ditching findOne, you should use findOneBy for the queries without relations, and if you want relations, simply use find.
const users = await userRepository.find({
where: { /* conditions */ },
relations: { /* relations */ }
})
// users[0] if you want a first row
here is the #gradii/fedaco orm how to implement this feature.
use can use multi relations by method with
export class User extends Model {
#HasOneColumn({related: Profile})
profile;
}
export class Profile extends Model {
#HasOneColumn({related: Image})
image;
}
export class Image extends Model {
name;
}
eager load
User.createQuery().with('profile.image').get()
lazy load
const user = await User.createQuery().first();
const image await (await user.profile).image