TypeORM OneToMany relationship in the same entity - orm

I am struggling with creating a OneToMany relationship with the same entity. Is it possible in TypeORM?
Assuming I have an entity:
export class X {
#PrimaryGeneratedColumn()
id: number;
#OneToMany( what here???? )
tests: X[];
}
Can somebody help me? Thanks in advance.

Try this way:
export class X {
#PrimaryGeneratedColumn()
id: number;
#OneToMany(type => X, x => x.belongsToTest )
tests: X[];
#ManyToOne(type => X, x => x.tests)
belongsToTest: X;
}

Related

TypeORM: How to order by a relation count

Mu question is similar as this but with a bit difference.
I have a Singer entity and a related Song entity
Singer entity
export class Singer {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToMany( type => Song, Song => Song.user )
songs: Song[];
}
Song entity
export class Song {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#ManyToOne( type => Singer, Singer => Singer.songs )
singer: Singer;
}
I want to get all Songs ordered by Singer count
I also searched the a lot but can't find an answer... I just found how to order by the relation field like this:
songRepository.find({
order: {
singer: {
name: "ASC"
}
}
})
There's a way to solve this without QueryBuilder?

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
}
}

Many To Many Relation only accecepts Unique Values

Orders can have 0 to n Items in them. One Item can belongs to 0 to n Orders.
I have the Relationship set up the following way
#Entity()
export class Order {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => Customer, (customer) => customer.orders, {
eager: true,
})
customer: Customer;
#JoinTable()
#ManyToMany(() => Item, { eager: true })
items: Item[];
}
But I can only add Unique Items to my order. When I try to safe a item twice, it gets ignored?
This is the code for adding Items to a order
async addItemToOrder(orderId: number, itemId: number) {
const order = await this.findOne(orderId);
const item = await this.itemService.findOne(itemId);
if (!order.items) {
order.items = [];
order.items = [...order.items, item];
} else {
order.items = [...order.items, item];
}
order.totalPrice = this.calcTotalPrice(order.items);
await this.orderRepository.save(order);
return order;
}
This is the item
#Entity()
export class Item {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
price: number;
}
It is working as intended. In the underlying join table a itemId gets connected to a orderId. Typeorm only inserts a new entry if it cannot find a combination of itemId and orderId.
For your usecase it would make sense to define a own join table that includes a amount attribute. So your join table looks like this
itemId
orderId
amount
1
1
2
1
2
1
2
2
5
You can achive this using typeorm like this:
You create a new Entity that is the join entity between an item and an order and includes a attribute amount
#Entity()
export class OrderItem {
#Column('int')
amount: number;
#ManyToOne(() => Item, item => item.orders, { primary: true })
item: Item;
#ManyToOne(() => Order, order => order.items, { primary: true })
order: Order;
}
#Entity()
export class Order {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => Customer, (customer) => customer.orders, {
eager: true,
})
customer: Customer;
#OneToMany(() => OrderItem, { eager: true })
items: OrderItem[];
}
#Entity()
export class Item {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
price: number;
#OneToMany(() => OrderItem, { eager: true })
orders: OrderItem[];
}
The reason you do this is normalization. Relational databases rely on normalization to prevent inconsistent data.
You can read more about normalization here

TypeORM - Getting objects of provided id, which is one relation away

I want to get objects from table providing id, which is in relation with table, which is in another relation. It looks like this:
Hand is in relation manyToOne with Action (hand can have only one action),
Action is in relation manyToOne with Situation (action can have only one situation)
I'm trying to make GET request for hands in which I'm providing situationId.
Simplified entities:
#Entity()
export class Hand {
#PrimaryGeneratedColumn()
hand_id: number;
#Column()
hand: string;
#ManyToOne(type => Action, action => action.simplifiedhands, { eager: true, onDelete: 'CASCADE', onUpdate: 'CASCADE' })
action: Action;
}
#Entity()
export class Action {
#PrimaryColumn()
action_id: number;
#ManyToOne(type => Situation, situation => situation.actions, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
#JoinColumn({name: 'situation'})
situation: Situation;
#OneToMany(type => Hand, hand => hand.action)
hands: Hand[];
#OneToMany(type => Hand, hand => hand.action)
hands: Hand[];
}
#Entity()
export class Situation {
#PrimaryColumn()
situation_id: number;
#ManyToOne(type => Strategy, strategy => strategy.situations, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
strategy: Strategy;
#OneToMany(type => Action, action => action.situation)
actions: Action[];
}
What approaches didn't work for me so far (just example variants):
return await this.handsRepository.find({
relations: ["action", "action.situation"],
where: {
"situation": id
}
});
and
return await this.handsRepository.find({
join: {
alias: "hands",
leftJoinAndSelect: {
"action": "hand.action",
"situation": "action.situation"
}
},
where: {
"situation": id
}
});
Generally both 'works' but provide all the records, like there were no where condition.
The keys in the object you assign to where should be members of the entity of the repository, in your case Hand, since situation is a member of action it's not working. I'm surprised you didn't mention any errors.
You can do one of the following (example for postgres)
Using query builder:
return await this.handsRepository.createQueryBuilder(Hand, 'hand')
.leftJoin('hand.action', 'action')
.leftJoin('action.situation', 'situation')
.where('situation.id = :id', { id })
.getMany();
Or, you can try the following (success is not guaranteed):
return await this.handsRepository.find({
relations: ["action", "action.situation"],
where: {
action: {
situation: { id }
}
}
});

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

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