TypeORM bulk create with relations - mysql

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.

Related

Unknow column 'item.point_id' in 'field list' TypeOrm error

I am trying to pull some items from the database, but it is giving me the error where there is a unknown column. I am trying to do this.
public async getItemsFromIds(items): Promise<Item[]> {
const _items = await this.itemRepository
.createQueryBuilder('item')
// .innerJoinAndSelect('item.point_id', 'point')
.where('item.id IN (:...items)', { items: items })
.getMany();
return _items;}
This is the model I am using for the item:
export class Item {
#PrimaryGeneratedColumn({ name: 'id', type: 'bigint' })
protected _id: number;
get id(): number {
return this._id;
}
set id(id: number) {
this._id = id;
}
#Column({
name: 'name',
type: 'varchar',
length: 84,
nullable: false,
})
protected _name: string;
get name(): string {
return this._name;
}
set name(name: string) {
this._name = name;
}
#ManyToOne(() => Point, (point) => point.items)
#JoinColumn({ name: 'point_id' })
public _point: Point;
get point(): Point {
return this._point;
}
set point(point: Point) {
this._point = point;
}
}
I don't want to pull the points, just the items, but it seems that TypeORM is leading me to pull the points, but I don't want it at the moment. Does anyone know what to do?

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

Property 'filter' does not exist on type 'Object'. When trying to filter response

Im trying to get data from a json file that equal the name of the player in the url. For example: localhost:4200/players/Febiven should only return the information about Febiven. Im using Angular 6
So far I have this code:
player.service.ts
get(ingameName){
return <Observable<Player>> this.http.get(endpoint).map(response =>{
let data = response.filter(item=>{
if (item.ingameName == ingameName) {
return item
}
});
if (data.length == 1){
return data[0]
}
return {}
})
.catch(this.handleError)
}
private handleError(error:any, caught:any): any{
console.log(error, caught)
}
player-info.component.ts
export interface Player {
ingameName: string;
name: string;
intro: string;
image: string;
info: string;
team: string;
dob: string;
earnings: string;
role: string;
game: string;
favourite: string;
IDs: string;
}
export class PlayerInfoComponent implements OnInit {
players: Player[] = null;
private routeSub:any;
private req:any;
ingameName:string;
player : player;
constructor(private route: ActivatedRoute, private plService : PlayerService) { }
ngOnInit() {
this.routeSub = this.route.params.subscribe(params => {
this.ingameName = params['ingameName'];
this.req = this.plService.get(this.ingameName).subscribe(data=>{
this.player = data as player
})
});
Im getting the error 'Property 'filter' does not exist on type 'Object'. And I don't really have an idea how to fix this, I looked at multiple answers, but none seemed to work for me. If someone could help me with fixing this error thatd be great
Thanks
filter only exists on arrays. Your response is an object. You can do this instead:
get(ingameName){
return <Observable<Player>> this.http.get(endpoint).map(response =>{
let data = response.json();
if (data.ingameName == ingameName){
return data;
}
return {};
})
.catch(this.handleError)
}
Try this it will work:
define a parameter inside your class & use it in ngOnInit() function like this:
export class VideoDetailComponent implements OnInit, OnDestroy {
data_new:any;
ngOnInit() {
this.http.get("assets/json/videos.json").subscribe(data =>{
this.data_new = data;
this.data_new.filter(item=>{
console.log(item)
// do your work here
})
})
}
}

Angular 6: Fetch Hash Table Data from JSON respond Backend

I have This JSON respond from my backend:
//User_Courses
[
{
id: 1,
name: "Ice King",
email: "pretty_princess1234#gmail.com"
completedCourses: [1,3],
unlockedCourses: [1,3,4,5,6],
completedLessons: [{"1" => [1,2,3]}, {"3" => [1,2,3,4,5,6,7]}, {"4" => [1]}]
},
{
id: 2,
name: "Mr. Crocker",
email: "fairy_godparents111#gmail.com"
completedCourses: [3],
unlockedCourses: [1,3,4],
completedLessons: [{"3" => [1,2,3,4,5,6,7]}, {"4" => [1,2]}]
}
]
// completed lessons are all the lesson the user finished.
// courses can be in progress or completed.
I want to fetch data from backend and subscribe it to this interface.
I don't sure how to implement the data structure and how to access data.
This is the interface I created:
export interface IUser {
id: number;
name: string;
email: string;
completedCourses: number[];
unlockedCourses: number[];
completedLessons: // <----- don't know what type to write
}
I want to know how to implement this, subscribe data with service and access data (in order to change it later and add data).
Thank you so much!
Create model for CompletedLesson (as mentioned in the comments):
interface ICompletedLesson {
[name: string]: number[];
}
interface IUser {
id: number;
name: string;
email: string;
completedCourses: number[];
unlockedCourses: number[];
completedLessons: ICompletedLesson[];
}
Then, create a service, something like this:
#Injectable()
export class UserService {
constructor(private http: HttpService) { }
fetchUserCourses(): Observable<IUser[]> {
return this.http.get<IUser[]>(`URL_TO_THE_USER_COURSES%);
}
}
And, wherever you are fetching data (some component for example):
fetchUserCourses() {
// userService is injected in this component's constructor
this.userService.fetchUserCourses().subscribe(users => {
// do something with result, yes, something like
this.users = users;
});
}
In the JSON you provided, to access the first lesson of the Mr. Crocker completed lessons (this.users are all users you retrieved from backend):
const firstCompletedLesson = this.users[1].completedLessons[0]; // {"3": [1,2,3,4,5,6,7]}
const lessons = firstCompletedLesson["3"]; // [1,2,3,4,5,6,7]
const firstLesson = lessons[0]; // 1
Furhermore, you can access "3" like this:
Object.keys(firstCompletedLesson)[0]; // 3
and you can add to array using push:
lessons.push(8); // [1,2,3,4,5,6,7,8]
and to add new completed lesson use:
this.users[1].completedLessons.push({ "5": [1, 2, 3] });
Hope this helps.