i have three entities TestUser , TestProfile and TestPhoto in which TestUser has a OneToOne relationship with TestProfile and TestProfiles has a OneToOne relationship with TestPhoto and at the las TestPhoto has this ManyToOne relationship with User which might has not been created yet
im using cascade when defining my entites and i wish to have them all get created with a single call in my UserService but facing this Cyclic dependency: "TestPhoto" Error and had no progress since then , i see its not probably what is should do in real life scenarios but apart from that ,any possible hack for it or its just fundamentally not possible?
#Entity()
#Unique(["name"])
export class TestUser {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToOne(() => TestProfile,{
cascade:true,
nullable:true
})
#JoinColumn()
profile: TestProfile;
#Column({nullable:true})
profileId: number
#OneToMany(() => TestPhoto, photo => photo.user)
photos: TestPhoto[];
}
#Entity()
export class TestProfile {
#PrimaryGeneratedColumn()
id: number;
#Column()
gender: string;
#OneToOne(type=>TestPhoto,{
cascade:true,
nullable:true
})
#JoinColumn()
photo: TestPhoto;
#Column({nullable:true})
photoId: number
}
#Entity()
export class TestPhoto {
#PrimaryGeneratedColumn()
id: number;
#Column()
url: string;
#ManyToOne(() => TestUser, user => user.photos,{
cascade:true,
nullable:true
})
user: TestUser;
#Column({nullable:true})
userId: number;
}
and in my UserService abstracted the calls as followed
const user = new TestUser();
const profile1 = new TestProfile();
const photo1 = new TestPhoto();
photo1.user = user;
profile1.photo = photo1;
user.profile = profile1
await connection.manager.save(user);
Does these entities are living in the same file?
I use import type TS's feature to resolve cyclic dependencies at module resolution level. I'm not sure if that is your case tho.
Before you write code please feel free to understand the concept of circular dependency; Link. It is possible to have circular dependency in your case but might not be in real life scenarios. What you have to do is make your Entity/Modal a forwardRef on both side. Then make services inject-able to others using #Inject(forwardRef(() => YourService)) inside constructor of another service. If you did not get an idea I will post a complete example of how circular dependency works in your case and in real life scenarios.
I solved this issue with typeorm Relation as explaine here tyopeorm entities circular dependencies
I use a common import file when there is a circular dependency problem:
// common.ts
// also it's important to keep order, parent first and then...
export * from './parent.entity';
export * from './child1.entity';
export * from './child2.entity';
// parent.entity.ts ------------------------------------
import { Child1Entity, Child2Entity } from './common.ts'
export class ParentEntity {
#OneToOne(() => Child1Entity, child => child.parent)
public child1: Child1Entity[];
#OneToOne(() => Child2Entity, child => child.parent)
public child2: Child2Entity[];
}
// child1.entity.ts ----------------------
import { ParentEntity } from './common.ts'
export class Child1Entity extends Parent {
#OneToOne(() => ParentEntity, parent => parent.child1)
#JoinColumn()
public parent: ParentEntity;
}
// child2.entity.ts ----------------------
import { ParentEntity } from './common.ts'
export class Child2Entity extends Parent {
#OneToOne(() => ParentEntity, parent => parent.child2)
#JoinColumn()
public parent: ParentEntity;
}
This pattern can help you with various circular dependency problems
Related
When using a global interceptor like the transform interceptor in the docs, the parent entity passed to #ResolveField() is serialized, which causes properties decorated with #Exluded({ toPlainOnly: true }) to be undefined. Is there a way to always get the raw entity instance passed to #ResolveField() instead?
This is particularly annoying when entities have relation related ID fields specified:
import { Exclude } from 'class-transformer';
#ObjectType()
class Book {
#ManyToOne(() => Author)
author: Author;
#Exlude({ toPlainOnly: true })
authorId: number;
}
#ResolveField(() => Author, { name: 'author' ])
getAuthorForBook(#Parent() book: Book) {
// book.authorId does not exist
}
category.ts
#Entity('categoryenter code here')
export class Category{
#PrimaryGeneratedColumn({ type: 'int' })
id: Category;
#OneToMany(() => Category, category => category.category,{eager:true})
categoryList: Category[];
#ManyToOne(() => Category, (category) => category.categoryList)
category: Category;
}
The Category entity is above(mysql).
I want to find a category with all it's children like this
await categoryRepo.findOne({
where:{ id: 1 },
relations:['categoryList']
})
But I got an error Maximum call stack size exceeded
What am I suppose to do
Actually, as I see, you are trying to make a tree data structure. TypeORM has some decorators for that. Here is an example:
import {
Entity, BaseEntity, Column,
PrimaryGeneratedColumn, Tree,
TreeParent, TreeChildren
} from 'typeorm';
#Tree('materialized-path')
#Entity({ name: 'Menu' })
export class Category extends BaseEntity {
#PrimaryGeneratedColumn({ type: 'int' })
id: number;
#Column({ type: 'varchar', length: 50 })
text: string;
// Check bellow
#TreeParent()
parent: Category;
#TreeChildren()
children: Category[];
}
The decorator #Tree() is used to tell to TypeORM that every Instance has self references for itself. Every item should have one parent, and should have several children. The ancestors and descendant can be setted with the decorators #TreeParent() and #TreeChildren() respectively. Check the documentation for more details about the different modes available for #Tree() decorator.
Since you have eager loading, each Category object is trying to load all its children eligible for categoryList. And since categoryList is also a list of Category entities, all it's children are also trying to load categoryList of their own. And this goes on and on until the stack is overflowed.
Remove eager loading from Category entity:
#Entity('categoryenter code here')
export class Category{
#PrimaryGeneratedColumn({ type: 'int' })
id: Category;
#OneToMany(() => Category, category => category.category)
categoryList: Category[];
#ManyToOne(() => Category, (category) => category.categoryList)
category: Category;
}
I have a service where I'm trying to save an entity with relation entities(one to many).What I have is:
Payment entity:
#Entity('payment')
export class PaymentEntity {
#PrimaryColumn()
id: number;
#OneToMany(type => PaymentExamEntity, paymentExam => paymentExam.payment, { cascade: true })
paymentExams: PaymentExamEntity[];
...
}
PaymentExamEntity:
#Entity('payment_exam')
export class PaymentExamEntity {
#PrimaryColumn()
id: number;
#ManyToOne(type => PaymentEntity, payment => payment.paymentExams)
#JoinColumn({name: 'payment_id'})
payment: PaymentEntity;
....
}
PaymentService:
#Injectable()
export class PaymentService {
constructor(#InjectRepository(PaymentEntity) private paymentRepo: Repository<PaymentDTO>,
#InjectRepository(PaymentExamEntity) private paymentExamRepo: Repository<PaymentExamDTO>) { }
async create(data: PaymentDTO) {
const payment = this.paymentRepo.create(data);
await this.paymentRepo.save(payment);
for(let item of data.paymentExams){
item.payment = payment;
const paymentExams = this.paymentExamRepo.create(data.paymentExams);
this.paymentExamRepo.save(data.paymentExams);
}
return payment;
}
I can see that the payment entity is saved but the paymentExams entities failed to saved due to missing foreign key value.
[Nest] 11545 - 01/01/2021, 12:15:52 AM [ExceptionsHandler] ER_NO_DEFAULT_FOR_FIELD: Field 'payment_id' doesn't have a default value
The problem is that the payment object does not updated with auto generated id from database after save.Thus the assignment of foreign key is null. Is there any other solution for this. How can I store child entities?
Thanks in advance
save method returns an object of type Payment with id field. You can store it in a variable and use it in item.payment assignment.
Ok I managed to make it work with the following changes:
1. Payment entity changed id field annotation from #PrimaryColumn() to #PrimaryGeneratedColumn()
2. PaymentExam entity changed id field annotation from #PrimaryColumn() to #PrimaryGeneratedColumn()
In the payment service I can have only this:
async create(data: PaymentDTO) {
const payment = this.paymentRepo.create(data);
await this.paymentRepo.save(data);
return payment;
}
Also I have Auto Increment for the primary key in this 2 tables using mysql database
I'm using Nest.js, and considering migrating from TypeORM to Mikro-ORM. I'm using the nestjs-mikro-orm module. But I'm stuck on something that seems very simple...
I've 3 entities, AuthorEntity, BookEntity and BookMetadata. From my Author module, I try to left join the Book and BookMetadata tables with the createQueryBuilder method. But when running my query, I'm getting an error where Collection<BookEntity> of entity AuthorEntity[3390] not initialized. However columns from the Author table are well retrieved.
My 3 entities:
#Entity()
#Unique({ properties: ['key'] })
export class AuthorEntity {
#PrimaryKey()
id!: number;
#Property({ length: 255 })
key!: string;
#OneToMany('BookEntity', 'author', { orphanRemoval: true })
books? = new Collection<BookEntity>(this);
}
#Entity()
export class BookEntity {
#PrimaryKey()
id!: number;
#ManyToOne(() => AuthorEntity)
author!: AuthorEntity;
#OneToMany('BookMetadataEntity', 'book', { orphanRemoval: true })
bookMetadata? = new Collection<BookMetadataEntity>(this);
}
#Entity()
#Unique({ properties: ['book', 'localeKey'] })
export class BookMetadataEntity {
#PrimaryKey()
id!: number;
#Property({ length: 5 })
localeKey!: string;
#ManyToOne(() => BookEntity)
book!: BookEntity;
}
And the service file where I run my query:
#Injectable()
export class AuthorService {
constructor(
#InjectRepository(AuthorEntity)
private readonly authorRepository: EntityRepository<AuthorEntity>,
) {}
async findOneByKey(props: { key: string; localeKey: string; }): Promise<AuthorEntity> {
const { key, localeKey } = props;
return this.authorRepository
.createQueryBuilder('a')
.select(['a.*', 'b.*', 'c.*'])
.leftJoin('a.books', 'b')
.leftJoin('b.bookMetadata', 'c')
.where('a.key = ?', [key])
.andWhere('c.localeKey = ?', [localeKey])
.getSingleResult();
}
}
Am I missing something? Might be not related, but I also noticed that there is a special autoLoadEntities: true for TypeORM users using Nest.js. Is there something similar for Mikro-ORM? Thanks ;)
Mapping of multiple entities from single query is not yet supported, it is planned for v4. You can subscribe here: https://github.com/mikro-orm/mikro-orm/issues/440
In v3 you need to use 2 queries to load 2 entities, which for your use case is much easier without the QB involved.
return this.authorRepository.findOne({ key }, ['books']);
Or you could use qb.execute() to get the raw results and map them yourself, but you would also have to manually alias all the fields to get around duplicities (Author.name vs Book.name), as doing qb.select(['a.*', 'b.*']) will result in query select a.*, b.* ... and the duplicate columns would not be correctly mapped.
https://mikro-orm.io/docs/query-builder/#mapping-raw-results-to-entities
About the autoLoadEntities thing, never heard of that, will take a look how it works, but in general, the nestjs adapter is not developed by me, so if its something only nest related, it would be better to ask on their GH repo.
Or you could use folder based discovery (entitiesDirs).
here is the new example with 3 entities:
return this.authorRepository.findOne({
key,
books: { bookMetadata: localeKey } },
}, ['books.bookMetadata']);
This will produce 3 queries, one for each db table, but the first one will auto-join books and bookMetadata to be able to filter by them. The condition will be propagated down in the second and third query.
If you omit the populate parameter (['books.bookMetadata']), then only the first query will be fired and you will end up with books not being populated (but the Author will be queried with the joined condition).
I have two different types of users - let's say vendor and labour. They are non-overlapping and non-covering (i.e., some users may be neither vendor nor labour and a vendor can never be a labour).
The SQL representation would look like the following:
CREATE TABLE users(id INTEGER PRIMARY KEY, ...)
CREATE TABLE vendors(id INTEGER PRIMARY KEY REFERENCES users(id), ...)
CREATE TABLE labour(id INTEGER PRIMARY KEY REFERENCES users(id), ...)
But what is a good way to represent this with TypeORM?
This is one possibility but it isn't a good design:
#Entity('users')
export class User extends BaseEntity {
#PrimaryGeneratedColumn()
public id: number;
#OneToOne(type => Vendor, v => v.user, { nullable: true })
vendor: Vendor;
#OneToOne(type => Labour, l => l.user, { nullable: true })
labour: Labour;
}
#Entity('vendors')
export class Vendor extends BaseEntity {
#PrimaryColumn()
public id: number;
#OneToOne(type => User, u => u.vendor)
#JoinColumn({ name: 'id' })
user: User;
}
...
Is there a better approach? Is it better to just leave out the reverse relations?