TypeORM cascade option: cascade, onDelete, onUpdate - cascade

Do cascade options in TypeORM overlap or do they have a completely different purpose? Their description in the documentation is very scarce and partly missing, or I couldn't find it.
IOW, do the following options
{ cascade: "update" } = { onUpdate: 'CASCADE' }
{ cascade: "remove" } = { onDelete: 'CASCADE' }
have the same effect?
Or the cascade option is only for the TypeORM use while onUpdate and onDelete are only for the DB schema (created by migration)?

This is my conclusion of looking into it:
The cascade option does not affect the database column constraints, and I believe is used by TypeORM only in evaluating how to save entity relations to the database. We can define entities like this:
#Entity()
class Book extends BaseEntity {
#ManyToOne(() => Author, (author) => author.books, {
onDelete: 'CASCADE',
})
public author?: Author
}
#Entity()
class Author extends BaseEntity {
#OneToMany(() => Book, (book) => book.author, {
cascade: true,
})
public books: Book[];
}
onDelete sets the authorId foreign key to CASCADE onDelete on Book. This means that when the author is deleted, the book is also deleted.
Setting cascade: true on Author tells TypeORM that if a new book is appended on an author and the author is saved, the new book should also be saved to the database. Like this:
const author = await Author.findOne({ id: '123' });
author.books.push(new Book(...));
await author.save();
If cascade is not set on Book, the new book will not be saved to the database.

onDelete: 'CASCADE isn't supported in OneToMany relations yet. More context here: https://github.com/typeorm/typeorm/issues/1913

Related

TypeORM How to access reference relation field without loading relation entity

As we know, to create ManyToOne/OneToMany relation we have to use #ManyToOne/#OneToMany decorators on a field.
In my project I have two entities: Project and Position.
This is how I created a relation:
#Entity('positions')
export class Position {
#ManyToOne(() => Project, {
nullable: false,
eager: true,
})
#JoinColumn()
project: Project;
}
TypeORM documentation says this code will create projectId FOREIGN KEY column in the database and store a project id in it.
Then when we trying to access project property TypeORM loads a project by the id stored in projectId field.
QUESTION
How can I access that pojectId field without loading a relational entity?
The property projectId does not exists by default in Position entity and if I manually create it it is not populated by projectId column value.
I have tried this way:
#ManyToOne(() => Project, {
nullable: false,
eager: false,
})
#JoinColumn()
project: Project;
projectId: string;
You can use the #RelationId decorator exported by typeorm. Using your example:
import {
Column,
Entity,
ManyToOne,
RelationId,
JoinColumn,
} from 'typeorm'
#Entity()
export class Position {
#ManyToOne(() => Project, {
nullable: false,
eager: false,
})
#JoinColumn()
project: Project;
#Column()
#RelationId((position: Position) => position.project)
projectId: string;
}

How can I have a Sequelize composite key a (non-unique, unique) pair and then use it to map associations?

I have these relations in my SQL database. There are two entity types A and B. These entities are owners of a resource and I manage this via a mapping table. The relations are:
Entity A has one or more Owner entries.
Entity B has one or more Owner entries.
Each Owner entry has zero or one Entity A entry.
Each Owner entry has zero or one Entity B entry.
Each Resource has zero or more owners.
Each Owner entry tracks one resource.
I am using this to model individuals and groups owning a resource. The problem is when I go to use the ORM it complains that I have not given an association between Owners and Entities. I'm not really sure how to do this with Sequelize. What I would like to do is have a composite key like so:
(entity_type, entity_id)
where entity_type is from enum (EntityA, EntityB) and entity_id is an id corresponding to a record in either of those tables. In this way I can guarantee that the composite key is unique. But again, I'm not really sure how I can implement this via Sequelize.
While make associate, we can use where condition as below code.
Model : Owner.js
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Owner extends Model {
static associate(models) {
// define association here
this.belongsTo(models.EntityA, {
foreignKey: "entity_id",
as: "A",
where: { entity_type: 0 }
});
this.belongsTo(models.EntityB, {
foreignKey: "entity_id",
as: "B",
where: { entity_type: 1 }
});
}
}
Owner.init({
entity_type: DataTypes.INTEGER,
entity_id: DataTypes.INTEGER,
}, {
sequelize,
modelName: "Owner"
});
return Owner;
};
db.Owner.findAll({
include: 'A', 'B'
});

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

setting up a one to many relationship for a self referencing table

I have a Project entity with a non-autogenerated id field and a successor field. This successor is the project that follows next. But maybe there is no following project so this might be null.
#Entity()
export class Project extends BaseEntity {
#PrimaryColumn({ unique: true })
public id: string;
#OneToMany(() => Project, project => project.id, { nullable: true })
public successorId?: string;
}
When creating a new project via
public createProject(id: string, successorId?: string): Promise<Project> {
const project: Project = new Project();
project.id = id;
project.successorId = successorId;
return project.save();
}
there are multiple cases I have to take care for.
Passing in an id that already exists:
This will not throw an error. It just overrides the existing entity.
Passing in undefined for the successorId:
The code works fine then but it does not create a successorId column with null then. The column simply does not exist in the database.
Passing in the same id for id and successorId (this should be possible):
TypeORM throws the error
TypeError: Cannot read property 'joinColumns' of undefined
Passing in a successorId of another existing project:
I'm getting the same error as above
Passing in a successorId of a project that doesn't exist:
I'm getting the same error as above
So how can I fix that? I think my entity design seems to be wrong. Basically it should be
One project might have one successor
A project can be the successor of many projects
Would be awesome if someone could help!
Update
I also tried this
#OneToMany(() => Project, project => project.successorId, { nullable: true })
#Column()
public successorId?: string;
but whenever I want to call the createProject method I'm getting this error
QueryFailedError: null value in column "successorId" violates not-null
constraint
and this
#OneToMany(() => Project, project => project.successorId, { nullable: true })
public successorId?: string;
but then I'm getting this error
TypeError: relatedEntities.forEach is not a function
Please try this solution
#Entity()
export class Project extends BaseEntity {
#PrimaryColumn({ unique: true })
public id: string;
#Column({ nullable: true })
public successorId?: string;
#ManyToOne(() => Project, project => project.id)
#JoinColumn({ name: "successorId" })
public successor?: Project;
}
public successor?: Project; - property is used for building relation between entities (same entity in this case). Related entity must be specified as a property type, because TypeORM uses this type to determine target entity and build relation metadata. You can read more about TypeORM relations here
public successorId?: string; - property is just an "extracted" join column. When you use ManyToOne relation, TypeORM automatically creates a column in the database named propertyName + referencedColumnName (successorId in this case). But you cannot use this column in your code, because it is defined only in table and not in your class. And if you need this column defined in class (for further saving or displaying) you can create a property and mark it with a #Column decorator. Property name must be the same as the join column name in the table. Described in more detail here
Creating an entity with the same id just overrides the existing one
this is an expected behaviour. When you trying to save entity with an existing Id, TypeORM recognizes this as an update, not a create
You can try this
export class RolesPermission {
#PrimaryGeneratedColumn('uuid')
#PrimaryColumn({ type: 'varchar', length: 36, default: 'UUID()' })
entityId?: string;
#Column({ unique: true })
name: string;
#OneToMany(() => RolesPermission, (rolePermission) => rolePermission.parent)
rolePermissions?: RolesPermission[];
#ManyToOne(() => RolesPermission, (rolePermission) => rolePermission.rolePermissions, { nullable: true, createForeignKeyConstraints: false })
parent?: RolesPermission;
#Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
createdAt?: Date;
#Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' })
updatedAt?: Date;}

Setting foreignKey of Ember Data model

It looks like during Revision 3 of Ember Data 1, you could set the foreign key:
App.Model = DS.Model.extend({
namingConvention: {
// LOUD NAMING CONVENTION
// Changes fooKey to FOOKEY
keyToJSONKey: function(key) {
return key.toUpperCase();
},
// Determines the name of foreign keys in
// belongsTo relationships
foreignKey: function(key) {
return key.toUpperCase()+"_ID";
}
}
});
That does not seem to work now (currently Revision 7). How do you set the foreign key?
You may be looking for keyForBelongsTo. See http://emberjs.com/api/data/classes/DS.RESTSerializer.html
App.ApplicationSerializer = DS.RESTSerializer.extend({
keyForBelongsTo: function(type, name) {
return this.keyForAttributeName(type, name) + "_id";
},
});