Multiple OneToOne relation with TypeOrm doesn't work - sql

I currently using NestJS 8.4.7 and TypeORM 0.3.10
I want to make two (or more) OneToOne relations in my entity class based on an existing SQL Database
Here is my code :
// Article Entity
#Entity({ name: 'node' })
export class Article {
#PrimaryGeneratedColumn('increment')
public articleId: number
#OneToOne(() => ArticleBody)
#JoinColumn({ name: 'articleId', referencedColumnName: 'bodyId' })
public body: ArticleBody
#OneToOne(() => ArticleTitle)
#JoinColumn({ name: 'articleId', referencedColumnName: 'titleId' })
public title: ArticleTitle
}
// Body Entity
#Entity({ name: 'article_body' })
export class ArticleBody {
#PrimaryGeneratedColumn()
public bodyId: number
#Column({ nullable: false })
public bodyValue: string
}
// Title Entity
#Entity({ name: 'article_title' })
export class ArticleTitle {
#PrimaryGeneratedColumn()
public titleId: number
#Column({ nullable: false })
public titleValue: string
}
And I have this error:
sqlMessage: ERROR [ExceptionsHandler] Unknown column 'Article__Article_body.titleId' in 'on clause'
sql: SELECT
`Article`.`articleId` AS `Article_nid`,
`Article__Article_body`.`bodyValue` AS `Article__Article_body_bodyValue`,
`Article__Article_title`.`titleValue` AS `Article__Article_title_titleValue`
FROM `Article` `Article`
LEFT JOIN `article_body` `Article__Article_body` ON Article__Article_body.titleId=`Article`.`articleId`
LEFT JOIN `article_title` `Article__Article_title` ON `Article__Article_title`.`titleId`=`Article`.`articleId`
WHERE (`Article`.`articleId` = 1)
It looks like if the last JoincolumnOptions overwrites all previous JoincolumnOptions

// Article Entity
#Entity({ name: 'node' })
export class Article {
#PrimaryGeneratedColumn('increment')
public articleId: number;
#OneToOne(() => ArticleBody)
#JoinColumn({ name: 'articleBodyId' })
public body: ArticleBody;
#OneToOne(() => ArticleTitle)
#JoinColumn({ name: 'articleTitleId' })
public title: ArticleTitle;
}
// Body Entity
#Entity({ name: 'article_body' })
export class ArticleBody {
#PrimaryGeneratedColumn("increment")
public bodyId: number;
#Column({ name: 'body_value', nullable: false })
public bodyValue: string;
}
// Title Entity
#Entity({ name: 'article_title' })
export class ArticleTitle {
#PrimaryGeneratedColumn("increment")
public titleId: number;
#Column({ name: 'title_value', nullable: false })
public titleValue: string;
}
In Article Entity your joinColumn name is same that's why the error is giving

Related

How to return the entity with its relations after saving it?

I am building a storage application, with GraphQL as the backend, using Typegraphql and TypeORM.
The categories need to be added separately and then when adding a product, you choose from a dropdown one of the available categories. This in turn passes the categoryId to the product in a one-to-many/many-to-one relationship.
Here is my Category entity:
import {
Entity,
PrimaryColumn,
Column,
BaseEntity,
Generated,
OneToMany
} from 'typeorm';
import Product from './Product';
#ObjectType()
#Entity('categories')
export default class Category extends BaseEntity {
#Field()
#PrimaryColumn()
#Generated('uuid')
categoryId: string;
#Field()
#Column()
categoryName: string;
#OneToMany(() => Product, (product: Product) => product.category)
products: Product[];
}
and here is my Product entity
import {
Entity,
PrimaryColumn,
Column,
BaseEntity,
Generated,
ManyToOne,
JoinColumn
} from 'typeorm';
import Category from './Category';
#ObjectType()
#Entity('products')
export default class Product extends BaseEntity {
#Field()
#PrimaryColumn()
#Generated('uuid')
productID: string;
#Field()
#Column()
productName: string;
#Field(() => Category)
#ManyToOne(() => Category, (category: Category) => category.products, {
cascade: true,
lazy: true
})
#JoinColumn()
category: Category;
#Field()
#Column()
productQuantity: number;
#Field()
#Column({ type: 'decimal', precision: 2 })
productPrice: number;
#Field()
#Column({ type: 'decimal', precision: 2 })
productPriceRA: number;
#Field()
#Column({ type: 'decimal', precision: 2 })
productPriceKK: number;
#Field()
#Column('varchar', { length: 255 })
productSupplier: string;
#Field()
#Column('varchar', { length: 255 })
productOrderLink: string;
#Field()
#Column('longtext')
productImage: string;
}
For the save mutation, I've created an Input type as well:
export default class ProductInput implements Partial<Product> {
#Field()
productName: string;
#Field(() => String)
category: Category;
#Field()
productQuantity: number;
#Field()
productPrice: number;
#Field()
productPriceRA: number;
#Field()
productPriceKK: number;
#Field()
productSupplier: string;
#Field()
productOrderLink: string;
#Field()
productImage: string;
}
The relations work, as I am able to query the products, along with their category data with the following query:
{
getProducts {
productID
productName
category {
categoryId
categoryName
}
}
}
However, when saving a product it always returns
"message": "Cannot return null for non-nullable field Category.categoryName."
This is the Mutation's code in the Resolver:
#Mutation(() => Product, { description: 'Add new product' })
async addProduct(
#Arg('product') productInput: ProductInput
): Promise<Product | any> {
try {
const product = await Product.create(productInput).save();
console.log('product: ', product);
return product;
} catch (error) {
return error;
}
}
I've been trying different things, however nothing seems to work and I am wondering if it's even possible to directly return the entity with its relations. If it's not, the other option I can think of is to return true/false based on the result and re-query all of the data. But this seems very inefficient and I am actively trying to avoid going this route.
Any help will be much appreciated.
After some more research and I decided to go with the following approach:
try {
const { productID } = await Product.create(productInput).save();
return await Product.findOne(productID);
} catch (error) {
return error;
}
This allows me to directly return the product, based on the productID after it's saved in the database and properly returns the object with it's relationship.
GraphQL uses an notation to recognize data. You can see it as __typename object property. Of course, this must be turned on in the GraphQL server configuration. If you see it, it's already clear. You can reach the correct result without refetching the relation changes in the cached data on the client side with a trick like this.
For example, let's say we have updated the Product with category. In the data to return from the update mutation, it is sufficient to return only the id of the relation.
For this to work, category and product must be cached separately on the client beforehand.
for example:
mutation UpdateProduct($product: UpdateProductInput!) {
updateProduct(product: $product) {
id
title
category {
id
}
}
}
You can also write in writeFragment, which is a separate method, which is the most stingy, but it can make your job difficult in nested data.
export class ProductFragmentService {
constructor(private apollo: Apollo) {}
updateProduct(product: Product): void {
const client = this.apollo.client;
client.writeFragment({
id: `Product:${product.id}`,
fragment: gql`
fragment UpdateProductCategoryFragment on Product {
__typename
id
title
category {
id
}
}
`,
data: {
__typename: 'Product',
...product,
},
});
}
}
If you want all the fields belonging to category, you need to send them to resolver and return as a response from there. Otherwise, yes, it gives a warning that I could not find the name property.
The more profitable way of doing it is to send this data to the resolver with the input, as I wrote above, and return to the client as a response from the server.
If you still have to make another SQL request, it is necessary to call the same id after registration.
#Authorized()
#Mutation(() => Product, { description: 'Add new product' })
async addProduct(
#Arg('product') productInput: ProductInput
): Promise<Product> {
await this.productRepo.save(productInput);
return await this.productRepo.findOne({ where: { id: productInfo.id } });
}
that's all :)

How to filter the find method for a field on its relations?

The structure of my application is that I have:
Contacts
Groups
Lanes (belong to groups)
GroupContacts (join table to assign a contact to a group and assign a lane)
My entities are:
lane.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
ManyToOne,
Unique,
OneToMany,
} from 'typeorm';
import { Group } from './group.entity';
import { GroupContact } from './groupcontact.entity';
#Entity('lanes')
#Unique('UQ_NAMES', ['group', 'name'])
export class Lane {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
sequence: number;
#Column({ nullable: false, default: 30 })
updateFrequencyDays: number;
#Column({
nullable: false,
default: () => 'CURRENT_TIMESTAMP',
type: 'timestamp',
})
createdAt: Date;
#ManyToOne(
type => Group,
group => group.lanes,
)
group: Group;
#OneToMany(
type => GroupContact,
groupcontact => groupcontact.lane,
)
groupContacts: GroupContact[];
}
groupcontact.entity.ts
import { Contact } from '../contacts/contact.entity';
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
import { Group } from './group.entity';
import { Lane } from './lane.entity';
#Entity('groups_contacts')
export class GroupContact {
#PrimaryColumn()
groupId: number;
#PrimaryColumn()
contactId: number;
#Column()
laneId: number;
#Column({
nullable: false,
default: () => 'CURRENT_TIMESTAMP',
type: 'timestamp',
})
createdAt: Date;
#ManyToOne(
type => Group,
group => group.contactConnection,
)
#JoinColumn({ name: 'groupId' })
group: Group;
#ManyToOne(
type => Contact,
contact => contact.groupConnection,
)
#JoinColumn({ name: 'contactId' })
contact: Contact;
#ManyToOne(
type => Lane,
lane => lane.groupContacts,
)
#JoinColumn({ name: 'laneId' })
lane: Lane;
}
contact.entity.ts
import { Group } from 'src/groups/group.entity';
import { GroupContact } from '../groups/groupcontact.entity';
import {
Entity,
Column,
PrimaryGeneratedColumn,
JoinTable,
ManyToMany,
OneToMany,
} from 'typeorm';
#Entity('contacts')
export class Contact {
#PrimaryGeneratedColumn()
public id: number;
#Column()
public firstName: string;
#Column()
public lastName: string;
#Column({
nullable: false,
default: () => 'CURRENT_TIMESTAMP',
type: 'timestamp',
})
public createdAt: Date;
#OneToMany(
type => GroupContact,
gc => gc.contact,
)
groupConnection: GroupContact[];
}
What I am trying to do is that I am trying to return all contacts that belong to a particular group. But when returning those contacts, I'd also like to return the groupConnection object.
Here is the code I am using:
async getContacts(groupId) {
const group = await this.findOneByIdOrThrow(groupId);
const contacts = await this.contactRepository.find({
//where: { groupConnection: { groupId: groupId } },
relations: ['groupConnection'],
});
return contacts;
}
So far, am I able to return what I wanted, which is a list of contacts together with their relations. But I have not found a way to filter those results to retrieve only the ones that belong to a particular groupId.
Where clause commented.
How do I filter for that?
I was able to fix it by using QueryBuilder
const contacts = await this.contactRepository
.createQueryBuilder('contacts')
.innerJoinAndSelect('contacts.groupConnection', 'groupConnection')
.where('groupConnection.groupId = :groupId', { groupId })
.getMany();

EntityColumnNotFound: No entity column "authors" was found. many to many relations on TypeORM and NestJS

I am trying to get the books per user from the get request
I have the following problem, it turns out that I am doing a many-to-many relationship but it indicates that the authors entity was not found, I have already searched the TypeORM documentation but I cannot find anything, this is my code:
book.controller.ts
#Controller('book')
export class BookController {
constructor(
private readonly _bookService: BookService
) { }
#Get('author/:authorId')
getBooksByAuthor(
#Param('authorId', ParseIntPipe) authorId: number,
): Promise<ReadBookDto[]> {
return this._bookService.getBooksByAuthor(authorId);
}
}
book.service.ts
#Injectable()
export class BookService {
constructor(
#InjectRepository(BookRepository)
private readonly _bookRepository: BookRepository,
#InjectRepository(UserRepository)
private readonly _userRepository: UserRepository
) { }
async getBooksByAuthor(authorId: number): Promise<ReadBookDto[]> {
const books: Book[] = await this._bookRepository.find({
// This is where I have the entity of authors, it should be noted that the entity of books if I have it
where: { status: status.ACTIVE, authors: In([authorId]) },
})
console.log(books)
return books.map(book => plainToClass(ReadBookDto, book));
}
}
book.entity.ts
import {
BaseEntity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
Entity,
JoinColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { User } from '../user/user.entity';
#Entity('books')
export class Book extends BaseEntity {
#PrimaryGeneratedColumn('increment')
id: number;
#Column({ type: 'varchar', length: 100, nullable: false })
name: string;
#Column({ type: 'varchar', length: 500 })
description: string;
#ManyToMany(type => User, user => user.books, { eager: true, primary: true})
#JoinColumn()
authors: User[];
#Column({ type: 'varchar', default: 'ACTIVE', length: 8 })
status: string;
#CreateDateColumn({ type: 'timestamp', name: 'created_at' })
createdAt: Date;
#UpdateDateColumn({ type: 'timestamp', name: 'updated_at' })
updatedAt: Date;
}
This is the user entity where I make the many to many relation
user.entity.ts
#Entity('users')
export class User extends BaseEntity {
#PrimaryGeneratedColumn('increment')
id: number;
#Column({ type: 'varchar', unique: true, length: 25, nullable: false })
username: string;
#Column({ type: 'varchar', nullable: false })
email: string;
#Column({ type: 'varchar', nullable: false })
password: string;
#OneToOne(type => UserDetails, {
cascade: true,
nullable: false,
eager: true,
})
#JoinColumn({ name: 'detail_id' })
details: UserDetails;
#ManyToMany(type => Book, book => book.authors)
#JoinTable({ name: 'user_books' })
books: Book[];
}
If you could help me find the error it would be very helpful
Thanks
You can use queryBuilder to get the books:
#Injectable()
export class BookService {
constructor(
#InjectRepository(BookRepository)
private readonly _bookRepository: BookRepository,
#InjectRepository(UserRepository)
private readonly _userRepository: UserRepository
) { }
async getBooksByAuthor(authorId: number): Promise<ReadBookDto[]> {
const books: Book[] = await this._bookRepository.createQueryBuilder('books')
.leftJoinAndSelect("books.authors", "users")
.where('books.status = :status',{status : status.ACTIVE})
.andWhere("users.id = :id ", { id: authorId })
.getMany();
console.log(books)
return books.map(book => plainToClass(ReadBookDto, book));
}
}

TypeORM bulk insert?

How do i bulk insert multiple records in 1 query so my db will be efficient
I want to create Office and insert multiple new equipments into that office.
Table/model code:
OFFICE
#Entity({ name: 'offices' })
export class Office extends Timestamps {
#OneToMany(() => Equipment, (equipment: Equipment) => equipment.office, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
})
equipment: Array<Equipment>;
}
EQUIPMENT
#Entity({ name: 'equipment' })
export class Equipment extends Timestamps {
#Column({
name: 'equipment_id',
type: 'int',
nullable: false,
width: 2,
default: 0
})
equipment_id: number;
#ManyToOne(() => Office, (office: Office) => office.equipment)
#JoinColumn({ name: 'office_id' })
office: Office;
}
I suggest you to use cascades property in relationships:
#Entity({ name: 'offices' })
export class Office extends Timestamps {
#OneToMany(() => Equipment, (equipment: Equipment) => equipment.office, {
cascade: true, // <= here
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
})
equipment: Array<Equipment>;
}
Now if you set equipment when creating an instance of Office, it automatically insert an Equipment instance into database:
await Office.create({equipment: ['whatever']}).save();
Or if you use data mapper approach with repositories based on this link:
const officeRepository = connection.getRepository(Office);
const office = new Office();
const equipment1 = new Equipment(); // and set your properties or make more instances
office.equipment = [equipment1]; // or more instances
await officeRepository.save(office);

typeorm composition with type-graphql : need mixins?

I'm trying to use TypeOrm composition together with type-graphql, but I can not find a way to declare the object types.
The goal is to have a final entity NewBudgetEntity composed with 3 entities : MyBaseEntity, BudgetHeaderEntity and BudgetHeaderEntity
I have run the code below and it works fine, but the graphql schema is not reflecting the table.
In the table, as expected, there are all the columns of the 3 entities.
But in type-graphql, because I've add the #ObjectType in the main entity, the fields header and corps are in the Graphql schema, whereas they are not in the table.
I think I need to use mixins to make sure that the final #ObjectType extends the 3 other #ObjectTypes related to the 3 other entities, but I'm quite confused how to do it. I read the doc of type-graphql about mixins, but can not get it work on my project.
Here is the code that is running well, but I don't want 'header' and 'corps' to be part of the graphql query
Ths for your help
import { Field, Float, ID, InputType, ObjectType } from "type-graphql";
import { BaseEntity, Column, Generated, Index, PrimaryColumn } from "typeorm";
export interface IIdentifier {
id: string;
}
export interface IBudgetHeader {
codeBudget: string;
natureBudget: string;
}
export interface IBudgetCorps {
montant: number;
nom: string;
isAffectable: boolean;
}
#ObjectType()
#InputType("myBaseEntityInputType")
export class MyBaseEntity extends BaseEntity implements IIdentifier {
#Field(() => ID, { nullable: false })
#PrimaryColumn({ type: "uuid" })
#Index()
#Generated("uuid")
id: string;
}
#ObjectType()
#InputType("BudgetHeaderEntityInputType")
export class BudgetHeaderEntity implements IBudgetHeader {
#Field({ nullable: false })
#Column({
type: "character varying",
length: 20,
nullable: false,
unique: true,
default: "",
})
codeBudget: string;
#Field({ nullable: true })
#Column({
type: "character varying",
length: 20,
nullable: true,
default: "",
})
natureBudget: string;
}
#ObjectType()
#InputType("BudgetCorpsEntityInputType")
export class BudgetCorpsEntity implements Partial<IBudgetCorps> {
#Field({ nullable: true })
#Column({
type: "character varying",
length: 250,
nullable: true,
default: "",
})
nom: string;
#Field(() => Boolean, { nullable: true })
#Column({
type: "bool",
default: false,
nullable: true,
})
isAffectable: boolean;
#Field(() => Float, { nullable: true })
#Column({
type: "float",
default: 0.0,
nullable: true,
})
montant: number;
}
#ObjectType()
#InputType("NewBudgetEntityInputType")
#Entity({
name: "newbudget",
})
export class NewBudgetEntity extends MyBaseEntity {
#Field(() => BudgetHeaderEntity)
header: BudgetHeaderEntity;
#Field(() => BudgetCorpsEntity)
corps: BudgetCorpsEntity;
}
#Resolver(NewBudgetEntity)
export default class NewBudgetEntityResolvers extends apiNewBudgetEntity {
#Query()
dummy(): string {
console.log("test");
return "test";
}
}
Example of query but I want to avoid 'header{} and corps{} and just having data {id codeBudget nom}
If you want to hide some property from being exposed in GraphQL schema, just don't put #Field decorator above the property. If the property should be mapped to db column, just use #Column decorator only.