I got two entities with some relationships going on. The problem is that the EntityManager only saves the main entity and sets the foreign keys to NULL. I have read a similar question that OP seems to have solved himself, but his answer leaves me clueless.
I'm used to ORMs being able to insert nested entities in one call. Does have TypeORM have such functionality?
The entities are at the bottom of the question to save space here. The objects are built up like this:
const whiteFlourEntity = new Ingredient();
whiteFlourEntity.name = "white flour";
whiteFlourEntity.percentage = 100;
const flourEntities: Ingredient[] = [whiteFlourEntity];
const waterEntity = new Ingredient();
waterEntity.name = "water";
waterEntity.percentage = 68;
waterEntity.waterPercentage = 100;
const yeastEntity = new Ingredient();
yeastEntity.name = "yeast";
yeastEntity.percentage = 1;
const saltEntity = new Ingredient();
saltEntity.name = "salt";
saltEntity.percentage = 2;
const formulaEntity = new Formula();
formulaEntity.name = "test formula";
formulaEntity.flours = flourEntities;
formulaEntity.water = waterEntity;
formulaEntity.yeast = yeastEntity;
formulaEntity.salt = saltEntity;
When I save all nested entities first and after that the main entity all relationships are set up right. But when I only save the main entity, the sub-entities are not saved.
createConnection({
/** */
"entities": [Ingredient, Formula]
}).then(async connection => {
// WITH THIS LINES ACTIVE ALL RELATIONSHIPS ARE SAVED CORRECTLY
// flourEntities.forEach((flour: Ingredient) => {
// connection.manager.save(flour);
// });
// await connection.manager.save(waterEntity );
// await connection.manager.save(yeastEntity );
// await connection.manager.save(saltEntity );
await connection.manager.save(formulaEntity);
}).catch(error => console.log(error));
#Entity()
export default class Ingredient {
#PrimaryGeneratedColumn()
id?: number;
#Column()
name!: string;
#Column()
percentage!: number;
#Column()
waterPercentage!: number = 0;
}
#Entity()
export class Formula {
#PrimaryGeneratedColumn()
id?: number;
#Column()
name!: string;
#ManyToMany(() => Formula)
#JoinTable()
flours!: Ingredient[];
#OneToOne(() => Ingredient)
#JoinColumn()
water!: Ingredient;
#OneToOne(() => Ingredient)
#JoinColumn()
yeast!: Ingredient;
#OneToOne(() => Ingredient)
#JoinColumn()
salt!: Ingredient;
#ManyToMany(() => Ingredient)
#JoinTable()
extras?: Ingredient[];
#ManyToMany(() => Formula)
#JoinTable()
subFormulas?: Formula[];
}
Related
I'm writing because I have a question while dealing with the mapping table. When creating a user on a web page, I want to put existing information and data called equipment. Each user can start with multiple equipment, so they created a mapping table like a picture to resolve the N:M relationship.
However, in order to put data in the mapping table in typeorm, you must first create a user object and an item object. After turning the loop as many times as the number of equipment set to the user to find the equipment number received from the web, we are creating an equipment object and inserting it into the mapping table.
Is there a better way than what I'm doing?
await Promise.all(
items.map(async (element) => {
const uItem = new UserItem();
uItem.item = element.item;
uItem.user = user;
uItem.period = element.period;
await transactionManager.save(uItem);
})
);
typeORM has an easy solution for it
So you define your 2 main entities like this
#Entity()
export class Item{
#PrimaryGeneratedColumn('uuid')
id: string; // or whatever you like
#OneToMany(() => UserItems, userItem => userItem.item, {
nullable: true,
cascade: true,
})
userItems: UserItem[];
...
#Entity()
export class User{
#PrimaryGeneratedColumn('uuid')
id: string; // or whatever you like
#OneToMany(() => UserItems, userItem => userItem.user, {
nullable: true,
cascade: true,
})
userItems: UserItem[];
...
And your mapping class as following:
#Entity()
export class UserItem{
#PrimaryGeneratedColumn('uuid')
id: string; // or whatever you like
#ManyToOne(() => User, {
onDelete: 'CASCADE', // decide on delete logic
})
user: User;
#ManyToOne(() => Item, {
onDelete: 'CASCADE', // decide on delete logic
})
item: Item;
...
I was studying TypeOrm and I'm trying to create an N+1 problem, but it's not happening properly. Company and employee have a 1:N relationship.
Could you tell me why N + 1 is not causing any problems? I've tried setting up Lazy and setting up Eager, but I've been doing left join continuously so that n + 1 doesn't cause problems.
entity
#Entity('COMPANY')
export class Company extends TimeStamped {
#PrimaryGeneratedColumn('increment')
companyId: number;
#Column({ type: 'varchar' })
companyName: string;
#OneToMany(() => Employee, (employee) => employee.company, {
onDelete: 'CASCADE'
})
employee: Employee[];
}
#Entity('EMPLOYEE')
export class Employee extends TimeStamped {
#PrimaryGeneratedColumn('increment')
employeeId: number;
#Column({ type: 'varchar' })
employeeName: string;
#ManyToOne(() => Company, (company) => company.employee)
#JoinColumn([{ name: 'companyId', referencedColumnName: 'companyId' }])
company: Company;
}
crud
#Injectable()
export class CompanyService {
constructor(
#InjectRepository(Company)
private readonly companyRepository: Repository<Company>
) {}
getAllCompany() {
return this.companyRepository.find({ relations: ['employee'] });
}
getCompany(companyId: number) {
return this.companyRepository.findOne(companyId, {
relations: ['employee']
});
}
setCompany(setComanyDto: SetCompanyDto) {
return this.companyRepository.save(setComanyDto);
}
}
#Injectable()
export class EmployeeService {
constructor(
#InjectRepository(Employee)
private readonly employeeRepository: Repository<Employee>,
#InjectRepository(Company)
private readonly companyRepository: Repository<Company>
) {}
getAllEmployee() {
return this.employeeRepository.find({
relations: ['company']
});
}
getEmployee(employeeId: number) {
return this.employeeRepository.findOne(employeeId, {
relations: ['company']
});
}
async setEmployee(setEmployeeDto: SetEmployeeDto) {
const employee: Employee = new Employee();
employee.employeeName = setEmployeeDto.employeeName;
employee.company = await this.companyRepository.findOne(
setEmployeeDto.companyId
);
return this.employeeRepository.save(employee);
}
}
I believe you have a good idea about what N+1 problem is. You can check this question if you need to understand it more clearly.
If you use eager loading, you will not see the N+1 problem anyway since it joins the related entity and return both entities in one query.
If you specify relations as you've done below, again you will not see the N+1 problem since it creates a join query and returns all in 1 single query.
this.companyRepository.find({ relations: ['employee'] });
To create the N+1 problem,
Update your Company entity like below:
#Entity('COMPANY')
export class Company extends TimeStamped {
#PrimaryGeneratedColumn('increment')
companyId: number;
#Column({ type: 'varchar' })
companyName: string;
#OneToMany(() => Employee, (employee) => employee.company, {
onDelete: 'CASCADE',
lazy: true
})
employee: Promise<Employee[]>
}
In your CompanyService, create a new function to simulate the N+1 problem like below:
#Injectable()
export class CompanyService {
async createNPlus1Problem() {
// Query all companies (let's say you have N number of companies)
// SELECT * FROM "COMPANY";
const companies = this.companyRepository.find();
// The following `for` loop, loops through all N number of
// companies to get the employee data of each
for(company of companies) {
// Query employees of each company
// SELECT * FROM "EMPLOYEE" WHERE "companyId"=?;
const employees = await company.employee;
}
}
}
So in the above example, you have 1 query to get the company data. And N queries to get the employee data. Hence the N+1 problem.
Hope this clarifies your problem. Cheers 🍻 !!!
You can try to use this library https://github.com/Adrinalin4ik/Nestjs-Graphql-Tools it allows to overcome n+1 with the simple decorator. And it has minimum deps.
You can use leftJoinAndSelect method with query builder.
https://orkhan.gitbook.io/typeorm/docs/select-query-builder#joining-relations
const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.andWhere("photo.isRemoved = :isRemoved", { isRemoved: false })
.getOne()
SELECT user.*, photo.* FROM users user
LEFT JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber'
I have a couple of questions about NestJS and TypeOrm.
First, how to pass an array of strings to DTO? I tried just to use :string[] type, but the compiler gives an error.
This is my Post entity:
#Entity('posts')
export class Post {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => User, user => user.posts, { cascade: true })
author: number;
#Column({ type: 'timestamp' })
date: Date;
#Column()
text: string;
#Column({ default: 0 })
likes: number;
#OneToMany(() => Photo, photo => photo.post, { cascade: true })
photos: Photo[];
}
And CreatePostDto:
export class CreatePostDto {
authorId: number;
date: Date;
text?: string;
// photos?: string[];
}
And the second question: How can i save to the repository every photo (keeping the connection with post), posts to the posts repo and update user by adding new post binded to him.
I tried something like this, but it won't work obviously.
async create(createPostDto: CreatePostDto) {
const post = this.postsRepository.create(createPostDto);
const user = await this.usersRepository.findOne(createPostDto.authorId);
return this.postsRepository.save({author: user, date: createPostDto.date, text: createPostDto.text});
}
What you missed here is saving photos before bind them with the post, here's an example:
async create(createPostDto: CreatePostDto) {
let photos:Array<Photo> = [] ; array of type photo entity
for(let urlPhoto of createPostDto.photos)
{
let photo = await this.imageRepository.save({url : urlPhoto }); you must save the photos first
photos.push(photo);
}
const user = await this.usersRepository.findOne(createPostDto.authorId);
return this.postsRepository.save({author: user, date: createPostDto.date, text:
createPostDto.text,photos:photos});
}
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number
#Column("simple-array")
names: string[]
}
I'm using NestJS with TypeORM
I got two entities with relation:
export class Device {
#PrimaryGeneratedColumn()
id: number;
#Column("macaddr")
mac: string;
#OneToMany(type => DeviceStatus, deviceStatus => deviceStatus.mac)
#JoinColumn()
status: DeviceStatus[]
#Column()
created: Date;
}
export class DeviceStatus {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(type => Device, device => device.mac)
#JoinColumn({ name: "mac", referencedColumnName: "mac" })
mac: Device;
#Column()
deviceState: number;
#Column()
created: Date;
}
I want to get Device but only it's latest DeviceStatus on its status property.
Now I am doing it like this:
const deviceQuery: Device = await this.deviceRepository
.createQueryBuilder("device")
.where('device.id = :id AND "device"."userId" = :user', {id: id, user: user.id})
.getOne();
const statusQuery: DeviceStatus = await this.deviceStatusRepository.createQueryBuilder("status")
.where('status.mac = :mac', { mac: deviceQuery.mac })
.orderBy('created', "DESC")
.limit(1)
.getOne();
deviceQuery.status = [statusQuery];
How can I do this using just one typeorm queryBuilder query?
I already tried this, but it doesn't map status property to Device, .execute() gets all DeviceStatus properties but it wont map the entities.
const deviceQuery: Device = await this.deviceRepository
.createQueryBuilder("device")
.leftJoinAndSelect(subquery => {
return subquery
.from(DeviceStatus, "status")
.innerJoin(subquery => {
return subquery
.select(["status2.mac"])
.addSelect("MAX(created)", "lastdate")
.from(DeviceStatus, "status2")
.groupBy("status2.mac")
}, "status3", 'status.created = "status3"."lastdate"')
}, "status", 'device.mac = "status"."mac"')
.where('device.id = :id AND "device"."userId" = :user', {id: id, user: user.id})
.getOne();
You can achieve this by joining status 2 times.
The second status join (next_status) condition should compare created date and be after the first joined status.
Then just check if next_status is null, that means that there is no status younger than the first join
Code is probably a better explanation:
const device: Device = await this.deviceRepository
.createQueryBuilder("device")
.leftJoinAndSelect("device.status", "status")
.leftJoin("device.status", "next_status", "status.created < next_status.created")
.where("next_status.id IS NULL")
// device.status is the latest status
I have a rather simple entity model where a user has basic information. If a user also has a provider function a OneToOne relation will be created to the provider table.
My issue is that if I update a user without any provider function it works as expected. The fields which changed get updated but no new entry gets created. If the user has a provider function, all fields of the user get updated and no new entry gets created. In the table of the provider information each updated creates a new entry and the new ID gets set in the user table.
#Entity()
export class Users {
#Column('text', {primary: true})
uid: string;
#Column('text', {nullable: true})
firstName: string;
#Column('text', {nullable: true})
lastName: string;
#Column('text')
email: string;
#Column('text')
password: string;
#Column('text', {nullable: true})
role: string;
#OneToOne(type => Providers, providerData => providerData.user, {cascade: true})
#JoinColumn()
providerData: Providers;
#OneToOne(type => Clients, clientData => clientData.user, {cascade: true})
#JoinColumn()
clientData: Clients;
#Column('bytea', {nullable: true})
photo: Uint8Array;
}
Update function:
async update(uid: string, dto: UpdateUserDto): Promise<Users> {
const userToUpdate = await this.usersRepository.findOne(uid);
try {
const user = new Users();
const provider = new Providers();
const client = new Clients();
user.email = dto.email;
user.firstName = dto.firstName;
user.lastName = dto.lastName;
user.photo = dto.photo;
user.role = dto.role;
Logger.log(dto.email);
provider.licensed = dto.licensed;
provider.notes = dto.notes;
provider.paymentOptions = dto.paymentOptions;
provider.speciality = dto.speciality;
user.providerData = provider;
user.clientData = client;
const updatedUser: Users = Object.assign(user, dto);
updatedUser.uid = uid;
Logger.log('Updated User with UID: ' + userToUpdate.uid);
return await this.usersRepository.save(updatedUser);
} catch (error) {
Logger.log('Error updating user: ' + error);
}
}
What am I doing wrong or what is a better solution?
You are creating a new user instead of updating the existing one. try to add this line
const editedUser = this.usersRepository.merge(userToUpdate ,updatedUser);
and then save it
return this.usersRepository.save(editedUser);