TypeORM: how to implement bidirectional relationship, multiple fields --> one entity type - one-to-many

I've created a 'document' entity:
e.g.
#Entity()
export class Document {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
path: string;
...
}
Multiple documents can be related to different entity types: post, userProfile etc
in the post entity for example, I have several fields which all specify document relationships.
#OneToOne(type => DocumentEntity)
#JoinColumn({ name: 'default_document' })
defaultDocument: DocumentEntity;
#OneToOne(type => DocumentEntity)
#JoinColumn({ name: 'featured_document' })
featuredDocument: DocumentEntity;
#OneToMany(type => DocumentEntity, document => document.post)
#JoinColumn({ name: 'other_documents' })
otherDocs: DocumentEntity[];
I'm unclear how to make the document relationships bidirectional.
I had hoped to have a single field on document like:
#ManyToOne(type => abstractEntity, entity => entity.document)
parentEntity: abstractEntity;
This way if I'm querying document entities for their parent relationships,
I would have a result like:
documents: [
{
id: 1,
name: 'document 1',
path: 'https://image.hosted.service/1.jpg',
parentEntityId: 23
},
{
id: 2
name: 'document 2',
path: 'https://image.hosted.service/2.jpg'
parentEntityId: 27
}
]
But Typeorm seems to want me to define an exact matching field for each parent relationship field on documentEntity like:
#Entity()
export class Document {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
path: string;
...
#OneToOne(type => PostEntity, post => post.defaultDocument)
postEntityDefaultDoc: PostEntity;
#OneToOne(type => PostEntity, post => post.featuredDocument)
postEntityFeaturedDoc: PostEntity;
#ManyToOne(type => PostEntity, post => post.otherDocs)
otherDocs: PostEntity[];
}
For the sake of simplicity in this example, there are no M:N relationships: document can have at most one parent.
It doesn't seem correct that I would have to define a new field on document entity, for every possible instance where a parent entity field references a document.
A query on document would not return a list with one field defining the parent entity, instead I have to parse/aggregate an arbitrary number of fields.
I can't seem to find any tutorials/examples in which a single entity has many fields each referencing the same other entity, which is making me think my basic approach is flawed.

The secret ingridient is leftJoinAndMapMany which allows you to join abitrary entities and map it onto attributes.
Here is what I would do in your case. The DocumentEntity would look like that:
#Entity()
class DocumentEntity {
#PrimaryGeneratedColumn()
public id!: number;
#Column()
public entity!: string;
#Column({
name: 'entity_id',
})
public entityId!: string;
#Column()
public name!: string;
}
Your PostEntity would look like that:
#Entity()
class PostEntity {
#PrimaryGeneratedColumn()
public id!: number;
#Column()
public name: string;
public documents?: DocumentEntity[];
}
As you might notice, the documents on the post has no anotation. Thats because we will do the join with the aforementioned method. Your query would look something like that:
connection
.getRepository(PostEntity)
.createQueryBuilder('p')
.leftJoinAndMapMany(
'p.documents',
DocumentEntity,
'p__d',
'(p.id = md.entityId AND md.entity = :documentEntity)',
{
documentEntity: PostEntity.name,
},
)
.getMany()
These methods are available for joining these entities:
leftJoinAndMapMany
innerJoinAndMapMany
leftJoinAndMapOne
innerJoinAndMapOne

Related

How do I join two entities linked with 1-1 relationship and add the joined entity on a particular property of the first entity?

I have 2 entities with a one to one relationship between them created with typeorm.
#ObjectType()
#Entity()
export class Interaction extends BaseEntity {
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Field(() => AF_Metadata)
#JoinColumn()
#OneToOne(() => AF_Metadata, { eager: true })
metadata: AF_Metadata;
}
}
#ObjectType()
#Entity()
export class AF_Metadata extends BaseEntity {
#Field()
#PrimaryGeneratedColumn()
id!: number;
//Relationships
#OneToOne(() => Interaction, { onDelete: "CASCADE" })
interaction: Interaction;
}
How do I join these entities by adding the AF_Metadata entity to the metadata property in the Interaction entity. I am able to add the whole AF_Metadata object to the interaction but not to the metadata propery. This is my sql query so far.
select * from interaction int
left join af_metadata metadata on int."metadataId" = metadata.id
order by int."createdAt" DESC
Current result:
{
id: 8327,
metadataId: 1,
}
Desired result:
{
id: 8327,
metadata: {
metadataId: 1
}
}

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 do I add a `relation` to my PostgresQL database using Typeorm?

For example
#Entity()
class Post {
#Column()
post_hash: string;
#Column()
title: string;
categorys: Array<Category> = [];
}
#Entity()
class Category {
#Column()
content: string;
#Column()
post_hash: number;
}
I want to query all the category content of the corresponding post through Typeorm.
I tried this method and failed.
this.createQueryBuilder('Post').leftJoinAndMapOne(
'Post.categorys',
Category,
'category',
'category.post_hash = post.post_hash'
)
This is my error message.
QueryFailedError: relation "post" does not exist
Entities
First of all, you must identify each row with a unique ID using #PrimaryColumn() or #PrimaryGeneratedColumn() decorators.
Each Post entity can have multiple categories and each Category entity is related to multiple posts.
This is called a many-to-many relation and must be correctly mapped using the #ManyToMany() decorator.
See this for more information.
#Entity()
class Post {
#PrimaryGeneratedColumn()
id: number;
#Column()
post_hash: string;
#Column()
title: string;
#ManyToMany(() => Category, category => category.posts)
#JoinTable()
categories: Category[];
}
#Entity()
class Category {
#PrimaryGeneratedColumn()
id: number;
#Column()
content: string;
#Column()
post_hash: number;
#ManyToMany(() => Post, post => post.categories)
posts: Post[];
}
Query
Obtain all Post(s) with the related categories.
Using find:
const posts: Post[] = await getRepository(Post)
.find({ relations: ['categories'] });
Using QueryBuilder:
const posts: Post[] = await getRepository(Post)
.createQueryBuilder('post')
.leftJoinAndSelect('post.categories', 'category')
.getMany();

About how to use many to one in TypeORM

For the table which has Foreign key, I want to assign ManyToOne's decorator.
I know #ManyToOne(() => User, user => user.photos) is just table relation,
What its argument () => User, user => user.photos means?
And please tell me user: User's property and value mean.
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import {User} from "./User";
#Entity()
export class Photo {
#PrimaryGeneratedColumn()
id: number;
#Column()
url: string;
#ManyToOne(() => User, user => user.photos)
user: User;
}
import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm";
import {Photo} from "./Photo";
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToMany(() => Photo, photo => photo.user)
photos: Photo[];
}
it just sets the inverse relationship so if you want you could query the other way back. For example:
await this.photoRepository.find({
loadEagerRelations: true,
relations: ['user'],
})
and you would have something like :
[
{
"id": 1,
"url": "https://twetew",
"user": {
"id": 1,
...
...
}
}
]
TypeORM needs this to understand the relation and create the correct reference. In your photos database table it will create a user_id column. User won't have a photo_id.
So:
#ManyToOne(() => User, user => user.photos)
user: User;
creates a user_id column on photos database table and lets you query the other way back.

TypeORM getRepository.find() does not include Foreign Key Fields

I am trying to fetch all the columns included on my entity, but I only able to fetch the columns that does not have any relationship from the other entity.
I use this block of codes to fetch the all the rows to this repository.
private translationTextRepository = getRepository(TranslationText);
async all(request: Request, response: Response, next: NextFunction) {
return this.translationTextRepository.find();
}
And here's the entity for this repository.
#Entity('TranslationText')
export class TranslationText {
#PrimaryGeneratedColumn()
ID: number;
#Column()
CreatedBy: string;
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
CreatedDate: Date;
#Column()
Status: boolean;
#Column({ nullable: true, default: null })
ModifiedBy: string;
#Column({ type: 'timestamp', nullable: true, default: null })
ModifiedDate: Date;
#Column()
Text: string;
#ManyToOne((type) => Locale, (locale) => locale.ID)
#JoinColumn({ name: 'LocaleID' })
LocaleID: Locale;
#ManyToOne((type) => TranslationTitle, (translationTitle) => translationTitle.ID)
#JoinColumn({ name: 'TranslationTitleID' })
TranslationTitleID: TranslationTitle;
}
But I was only able to fetch all the columns except the LocaleID and the TranslationTitleID.
How can I achieve this?
Check this document:
https://typeorm.io/#/relations-faq/how-to-use-relation-id-without-joining-relation
solution:
define new column:
#column()
LocaleID: number
rename old one to : Locale
But typeOrm cannot sync your table due to foreign key problem.
use eager option in #ManyToOne({eager: true})
The search result will contain relation Locale object, you can take id from it.
Can you try to specify the relations like that:
async all(request: Request, response: Response, next: NextFunction) {
return this.translationTextRepository.find({
relations:["LocaleID","TranslationTitleID"]
});
}
Because you have to make explicit that you want your relations on the query.