Nestjs, How to get entity table name? - nestjs

How to get entity table name ? (ex: member-pre-sale-detail)
I want to set table comment
// Seeder: Clear & set Comment
export default class ClearAllSeed implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<void> {
const deleteEntities = [
{table: OrderHead, comment: '訂單/主表'},
]
for(const entity of deleteEntities){
await connection
.createQueryBuilder()
.delete()
.from(entity.table)
.execute();
await connection
// >>>> but table name is MemberPreSaleDetail not member-pre-sale-detail
.query(`alter table ${entity.table.name} comment '${entity.comment}'`);
}
}
}
// Sampel Entity
#Entity('member-pre-sale-detail')
export class MemberPreSaleDetail {
#PrimaryGeneratedColumn({unsigned: true})
id?: number;
#Column({comment: '幾批(整批)', type: 'mediumint', default: 0})
batchQty: number;
}
Expected behavior
get the 'member-pre-sale-detail' string
Environment
Nest version: 7.0.7
For Tooling issues:
- Node version: v14.5.0
- Platform: Mac

I am guessing you are using TypeORM. In that case:
You could get the entity metadata by calling connection.getMetadata(MemberPreSaleDetail).
This method returns an EntityMetadata, which has name, tableName and givenTableName properties. For your usecase I guess you could simply use givenTableName.

Related

NestJS: How to convert Mongoose document to regular class?

I have followed NestJS mongo docs to create a schema (without ID) from a regular class. Since this class has other general uses (unrelated to mongoose), I would like to have regular methods on that class as well:
#Schema({ _id: false })
export class Location {
#Prop()
lat: number;
#Prop()
lon: number;
regularMethod() { return something based on this.lat, this.lon }
}
export const LocationSchema = SchemaFactory.createForClass(Location);
export type CatDocument = HydratedDocument<Cat>;
#Schema()
export class Cat {
#Prop({ type: [LocationSchema] })
locations: Location[];
}
export const CatSchema = SchemaFactory.createForClass(Cat);
The problem is that if I query such an object from the db, regularMethod doesn't exist since the queried object is actually a Document based on Location, rather than Location. The document only exposes methods that were defined by the schema.methods, which is not what I need.
MyService {
constructor(#InjectModel(Cat.name) catModel: Model<CatDocument>) {}
async findLocations(catId: string) {
const cat = await catModel.findOneById(catId);
cat.location.forEach(loc => loc.regularMethod()) // no such method
}
}
Is there some obvious way to "cast" Location to the original class to have access to those methods?

How to update an entity with relations using QueryBuilder in TypeORM

I have UserEntity and AddressEntity, they are related as OneToOne, that's one user may have only one address. UserEntity has fields firstName, secondName, address. AddressEntity has fields country and city.
If I wanted to update UserEntity without doing it to its relations I would do this:
await entityManager.getRepository(UserEntity)
.createQueryBuilder('users')
.update(UserEntity)
.set(updateUserObject)
.where('users.id = :userId', { userId })
.execute();
where updateUserObject is formed from a request body. That's to say, if I need to update firstName, the object would look like this: { firstName: 'Joe' }. Now what is unclear is how to use that builder if I have the following updateUserObject:
{
firstName: "Bob",
address: {
"city": "Ottawa"
}
}
The official documentation does not address such cases.
You can achieve this using preload and save methods.
Update your UserEntity similar to below:
#Entity('user')
export class UserEntity {
...
#OneToOne(
() => AddressEntity,
{
// Make sure that when you delete or update a user, it will affect the
// corresponding `AddressEntity`
cascade: true,
// Make sure when you use `preload`, `AddressEntity` of the user will also
// return (This means whenever you use any kind of `find` operations on
// `UserEntity`, it would load this entity as well)
eager: true
}
)
#JoinColumn()
address: AddressEntity;
}
Now using entityManager, you can update all the fields that you want using the following way:
const partialUserEntity = {
id: userId,
firstName: "Bob",
address: {
"city": "Ottawa"
}
};
const userRepository = await entityManager.getRepository(UserEntity);
// Here we load the current user entity value from the database and replace
// all the related values from `partialUserEntity`
const updatedUserEntity = userRepository.preload(partialUserEntity);
// Here we update (create if not exists) `updatedUserEntity` to the database
await userRepository.save(updatedUserEntity);
However, you need to make sure that your UserEntity has an AddressEntity associated always. Otherwise, you will have to generate an id for AddressEntity like below before you execute save method.
/*
* If `updatedUserEntity.address.id` is `undefined`
*/
// `generateIDForAddress` is a function which would return an `id`
const generatedIDForAddress = generateIDForAddress();
const partialUserEntity = {
id: userId,
firstName: "Bob",
address: {
"id": generatedIDForAddress,
"city": "Ottawa"
}
};
Please note that under the hood, typeorm will run UPDATE statements separately for UserEntity and AddressEntity. This is just an encapsulation of multiple join statements (when executing preload method) and update statements (when executing save method) such that the developer can easily implement this scenario.
Hope this helps you. Cheers 🍻!

Nestjs one to many relationship save issue

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

Attribute contains only ID, not whole entity, in persisted TypeORM entity

I've got this entity class:
#Entity("organization")
export class OrganizationEntity {
// ...
#PrimaryColumn({name: "party_id"})
#OneToOne(() => PartyEntity, {cascade: true})
#JoinColumn({name: "party_id", referencedColumnName: "id"})
party: PartyEntity
}
Then I create a new OrganizationEntity and persist it:
const savedOrganizationEntity = await this.organizationTypeOrmRepository.save(organizationEntity);
// see Repository.save
However, the returned savedOrganizationEntity contains a string in the field party, not a PartyEntity object.
How can I fix this behaviour, so that OrganizationEntity.party contains a PartyEntity, not a string?
The behaviour is working as designed: https://github.com/typeorm/typeorm/issues/3490

Nest.js + Mikro-ORM: Collection of entity not initialized when using createQueryBuilder and leftJoin

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).

Resources