I am trying to develop a small application to record cooking recipes. To do this, I declared 2 entities with nestJS allowing me to manage the recipes and another to manage the ingredients. I also created a 3rd entity to record the quantities of ingredients needed :
Database diagram
// recipe.entity.js
#Entity()
export class Recipe {
#PrimaryGeneratedColumn()
id: number
#Column('datetime')
createdAt: Date
#Column('datetime')
updatedAt: Date
#Column('varchar', { length: 100 })
title: string;
#Column('varchar', {nullable: true})
image: string;
#OneToMany(type => RecipeIngredients, recipeIngredients => recipeIngredients.recipe)
ingredients: RecipeIngredients[];
}
// ingredient.entity.js
#Entity()
export class Ingredient {
#PrimaryGeneratedColumn()
id: number
#Column('datetime')
createdAt: Date
#Column('datetime')
updatedAt: Date
#Column('varchar', { length: 100 })
name: string;
#Column('varchar', {nullable: true})
image: string;
#OneToMany(type => RecipeIngredients, recipeIngredients => recipeIngredients.ingredient)
recipes: RecipeIngredients[];
}
// recipe_ingredients.entity.js
#Entity()
export class RecipeIngredients {
#PrimaryGeneratedColumn()
id: number
#ManyToOne(type => Recipe, recipe => recipe.ingredients)
recipe: Recipe
#ManyToOne(type => Ingredient)
ingredient: Ingredient
#Column()
quantity: string;
}
First, I would like to be able to retrieve a recipe with the list of necessary ingredients:
const recipe = await this.recipesRepository.createQueryBuilder('recipe')
.where('recipe.id = :recipeId', {recipeId: _id})
.leftJoin('recipe.ingredients', 'recipe_ingredients')
.leftJoin('recipe_ingredients.ingredient', 'ingredient')
.getMany();
But this method returns only my recipe object without the ingredients...
[
{
"id": 1,
"createdAt": "2020-04-30T09:12:22.000Z",
"updatedAt": "2020-04-30T09:12:22.000Z",
"title": "Test",
"image": null
}
]
From there, I'm lost ... How can I get the list of my ingredients (at least the name and quantity fields) directly from my service?
Thank you in advance for your help.
Using leftJoin will able you to join data without its selection. It selects the recipe if it has ingredients, but won't return its ingredients.
As read in TypeORM's documentation :
You can join data without its selection. To do that, use leftJoin or
innerJoin:
const user = await createQueryBuilder("user")
.innerJoin("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.getOne();
This will generate:
SELECT user.* FROM users user
INNER JOIN photos photo ON photo.user = user.id
WHERE user.name = 'Timber'
This will select Timber if he has photos, but won't return his photos.
To select the ingredients try using leftJoinAndSelect instead.
Related
I wanted to create one to one relation in typeorm (NestJS). Just like in the picture below.
I wanted then to signup user with email and password and create row in custom_auth and user_profile table.
relations
I cant find solution to create that type of relation. I was only able to create typical relation using foreign key (solution in typeorm docs)
export class CustomAuth {
#PrimaryGeneratedColumn()
id: number
#Column({ unique: true })
email: string
#Column()
password: string
#OneToOne(() => UserProfile, (userProfile) => userProfile.customAuth, {
cascade: true,
})
userProfile: UserProfile
}
export class UserProfile {
#PrimaryGeneratedColumn()
userId: number
#Column()
name: string
#Column({ nullable: true })
address: string
#OneToOne(() => CustomAuth, (customAuth) => customAuth.userProfile, {})
#JoinColumn()
customAuth: CustomAuth
}
Here are my type-orm entities:
#Entity()
export class Conversation {
#PrimaryGeneratedColumn()
id: number;
#ManyToMany((type) => User, (user) => user.conversations)
attendees: User[];
}
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#ManyToMany((type) => Conversation, (conversation) => conversation.attendees)
#JoinTable()
conversations: Conversation[];
}
Having two different user IDs, I want to select a single conversation entity, which contains both of those users as attendees.
What I managed to get so far are the conversations of one user:
const conversationsUsers = await conversationRepository
.createQueryBuilder("c")
.innerJoinAndSelect("c.attendees", "user", "user.id = :authorId", { authorId })
.getMany();
Being far from becoming SQL expert, I think I should create another JOIN with users table (with an alias for e.g. user.id => attende_id, but have no idea how to approach this.
Thanks in advance for any thoughts :)
I'm having trouble updating an entity that has many-to-many.
Below are the product entities and categories.
#Entity()
class Products {
#PrimaryGeneratedColumn()
public id: number;
#Column()
public title: string;
#Column()
public content: string;
#ManyToMany((type) => Category, (category) => category.products)
#JoinTable()
#Type(() => Category)
categories: Category[];
}
#Entity()
class Category {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#ManyToMany((type) => Products, (products) => products.categories, {
cascade: true,
})
#Type(() => Products)
products: Products[];
}
Update in my service.
async updateProduct(id: number, product: UpdateProductDto) {
await this.service.update(id, product);
const updatedProduct = await this.service.findOne(id, {
relations: ['categories'],
});
if (updatedProduct) {
return updatedProduct;
}
throw new ProductNotFoundException(id);
}
I do not understand what exactly the problem is, since the addition works, the data in the database is there, I send a change request with data similar to how to create it.
{
"content": "test",
"title": "test",
"categories": [
{
"id": 3
}
]
}
Error message
[ExceptionsHandler] column "productsId" of relation "products" does not exist +23003ms
QueryFailedError: column "productsId" of relation "products" does not exist
at new QueryFailedError (...\node_modules\typeorm\error\QueryFailedError.js:12:28)
at PostgresQueryRunner.<anonymous> (...\node_modules\typeorm\driver\postgres\PostgresQueryRunner.js:248:31)
at step (...\node_modules\typeorm\node_modules\tslib\tslib.js:143:27)
at Object.throw (...\node_modules\typeorm\node_modules\tslib\tslib.js:124:57)
at rejected (...\node_modules\typeorm\node_modules\tslib\tslib.js:115:69)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
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
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