NestJs update Many-To-Many relation with join table - many-to-many

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.

Related

Duplicate column name with TypeORM in Nestjs

We have 2 entities: EstateIntegrationEntity and EstateEntity
When we try to use .findOne on the estateIntegrationRepository we get the following error:
[Nest] 5537 - 10/01/2020, 8:37:55 AM
[ExceptionsHandler] ER_DUP_FIELDNAME: Duplicate column name 'EstateIntegrationEntity_estate_id' +1590ms
QueryFailedError: ER_DUP_FIELDNAME: Duplicate column name 'EstateIntegrationEntity_estate_id'
at ...
We created a OneToOne relation from the EstateIntegrationEntity to the EstateEntity.
import { EstateEntity } from "src/estates/estate.entity";
import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, RelationId } from "typeorm";
#Entity({ name: 'estate_integrations' })
export class EstateIntegrationEntity {
#PrimaryGeneratedColumn()
id: number;
#RelationId((estateIntegrationEntity: EstateIntegrationEntity) => estateIntegrationEntity.estate)
estate_id: number;
#OneToOne(() => EstateEntity, { eager: true })
#JoinColumn({ name: 'estate_id' })
estate: EstateEntity;
...
}
And a relation from the EstateEntity to the EstateIntegrationEntity :
import { EstateIntegrationEntity } from 'src/integrations/estate.integration.entity';
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
#Entity('estates')
export class EstateEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar' || null)
uuid: string;
#OneToOne(
() => EstateIntegrationEntity,
estate_integration => estate_integration.estate,
)
estate_integration: EstateIntegrationEntity;
}
This error only occurs with .findOne() and not with .find():
async findEstateById(id: string): Promise<EstateIntegrationEntity> {
return await this.estateIntegrationRepository.findOne({
where: {
external_id: id
}
});
}
You can change the name of the fields. I just had a similar error and fixed it changing the name with #Column({ name: 'name_the_fields_with_different_name' }). Because the problem that if in the tables it has the same name when it does the find it has to fields with the same name.
For this case you have 3 solution for this:
1- change column name from 'estate_id' to anything else like 'id_estate'
2- write custom join statement with TypeORM
3- my favorite solution is to use name strategy like this:
first install npm i --save typeorm-naming-strategies
then in your typeorm config file
const SnakeNamingStrategy = require('typeorm-naming-strategies')
.SnakeNamingStrategy;
module.exports = {
name: 'name',
type: 'mysql',
host: 'localhost',
port: 3306,
...
namingStrategy: new SnakeNamingStrategy(),
}

NodeJS - TypeORM cascade insert using QueryBuilder

I'm trying to upsert an object array with a many-to-many relation to a MySQL table(s) using TypeORM query builder. Had to use the builder because repository save doesn't support upserts.
Problem is, while save cascades nested objects, QueryBuilder doesn't seem to (and RelationalQueryBuilder doesn't support upserts on MySQL).
Anyone know how I can do both? I want to both update and cascade, on an array.
First object:
#Entity()
export class Product {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: 'bigint', unique: true })
shopifyId: number;
#Column('int')
shopId: number | null;
#Column('varchar')
name: string | null;
#Column('varchar')
productType: string | null;
#Column('timestamp', { default: () => 'CURRENT_TIMESTAMP' })
updated: Date | null;
#ManyToMany(type => ProductTag, productTag => productTag.products, {
cascade: ['insert', 'update']
})
#JoinTable()
productTags: ProductTag[] | null;
}
Second object:
#Entity()
export class ProductTag {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar')
name: string | null;
#ManyToMany(type => Product, product => product.productTags)
products: Product[] | null;
}
Current upsert:
getConnection()
.createQueryBuilder()
.relation(Product, 'productTags')
.insert()
.into(Product)
.values(products)
.orUpdate({
conflict_target: ['shopifyId'],
overwrite: ['name', 'shopId', 'productType', 'updated']
})
.execute();
Thank you

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

TypeORM bulk create with relations

Is there a way to insert large amount of datas without blowing the JS heap memory ? I have a model which is Email as follow :
#Entity("email")
export class Email extends BaseEntity {
#PrimaryGeneratedColumn()
public id: number;
#ManyToOne((type) => Category, (cat) => cat.category, {nullable: false, cascade: ['insert']})
public category: Category;
#Column({type: "text", name: "email"})
public email: string;
}
and Category :
#Entity("category")
export class Category extends BaseEntity {
#PrimaryGeneratedColumn()
public id: number;
#Column({type: "text", name: "category"})
public category: string;
#OneToMany((type) => Email, (email) => email.category, {nullable: true})
public emails: Email[];
}
First problem I had is when I try to save {email: 'blabal#blalbah.com', category: 'default'} it says that Category must be an ID, but the thing is I want to add emails and create the category if it doesnt exist or asign the ID to the email if it exists. I did the following code :
public async bulkCreate(emails: Email[]): Promise<any> {
try {
const emailRepo = await getRepository(Email);
const categoryRepo = await getRepository(Category);
await Promise.all(emails.map(async (mail) => {
const cat = await categoryRepo.findOne({where: {category: mail.category}});
if (cat) {
// #ts-ignore
mail.category = cat.id;
} else {
const newCat = await categoryRepo.save(Object.assign(new Category(), mail));
// #ts-ignore
mail.category = newCat.id;
}
await emailRepo.save(mail);
}));
} catch (e) {
console.log(e);
throw new Error(e);
}
}
Worked for a few emails, but when I try to add even only 1,000 memory goes up to Like 4Gig and just crash.
What should I do? I'd like to add more than 1,000 emails at once.
I know it's little bit late, but solution for this use Bluebird Promise.map so you can define concurrency. instead executing in one run.

Declaring relationships in sequelize?

I've got three models I am trying to define a relationship between them.
Models:
User
Payment
Debtor
User has many Payments
Payment belongs to User
Payment has many Debtors
Debtor belongs to Payment
Debtor belongs to Payment
Do I need to define the relationship in the source and the target? i.e.
User.hasMany(Payment, {as: 'Payments'})
and also
Payment.belongsTo(User)
or can I get away with just defining it in one model?
I appear to get errors when trying to define it in both. The model I import is undefined and therefore when I try pass it into the "belongsTo()" or "hasMany()" functions I get the error called with something that's not an instance of Sequelize.Model
When I only include it in one model, it doesn't create the relationship correctly.
My models are as follows:
Payment model:
import { sequelize } from '../config';
import Sequelize from 'sequelize';
import User from './User';
const Payment = sequelize.define('payment', {
amount: {
type: Sequelize.FLOAT
},
reference: {
type: Sequelize.STRING
},
user_id: {
type: Sequelize.INTEGER
}
});
Payment.belongsTo(User)
export default Payment;
User model:
import { sequelize } from '../config';
import Sequelize from 'sequelize';
import Payment from './Payment';
const User = sequelize.define('user', {
email: {
type: Sequelize.STRING
},
username: {
type: Sequelize.STRING
},
firstName: {
type: Sequelize.STRING
},
lastName: {
type: Sequelize.STRING
},
facebookID: {
type: Sequelize.STRING
}
});
User.hasMany(Payment, {as: 'Payments'})
export default User;
// EDIT
When I try create a new user with a payment, I get the error payment is not associated to user!
exports.create = (req, res) => {
User.create({
email: 'test3#gmail.com',
firstName: "Test",
lastName: "Test",
username: 'fnord',
payment: [
{ amount: 10.00},
]
}, {
include: [ Payment ]
})
}
That happens because in payment you require User model and vice-versa, in user you require Payment (circular dependency). In order to avoid this situation you would have to declare both models in the same file or you could follow the Sequelize recommended way
module.exports = (sequelize, DataTypes) => {
return sequelize.define('modelName', {
// attributes
}, {
// additional options, like classMethods in which you could create the association
classMethods: {
associate: function(models) {
this.belongsTo(models.Model);
this.hasMany(models.Model);
}
}
});
}
In order to achieve it you would have to use the sequelize-cli module. If you install it, you could use sequelize init command which would generate a file which registers all models to the sequelize (all those models are initially in one location, however you can change it by modifying the auto generated file).
In this case, sequelize iterates over every model, registers it (with use of sequelize.import() method) and calls the associate function which creates relations between models. This way would prevent the situation you have mentioned.
if Someone is struggling with Sequelize associations like me, this might help for you as this is not mentioned any where in documentation that how you can make associations if you are using Sequelize.define i was also facing an issue and made this work for me
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: {
type: DataTypes.BIGINT,
primaryKey: true,
},
name: {
type: DataTypes.STRING
},
email: {
type: DataTypes.STRING,
unique : true,
},
password: {
type: DataTypes.STRING
},
status: {
type: DataTypes.STRING
},
});
let Bookmark = sequelize.model('Bookmark');
User.hasMany(Bookmark,{foreignKey : 'user_id', as 'bookmarks'});
return User;
};
hope it works!