Can I have EF code first TPH with a non abstract base class? - xaf

In have a table in SQL Server defined by
CREATE TABLE [dbo].[ReportDataV2](
[ID] [int] IDENTITY(1,1) NOT NULL,
[DataTypeName] [nvarchar](max) NULL,
[IsInplaceReport] [bit] NOT NULL,
[PredefinedReportTypeName] [nvarchar](max) NULL,
[Content] [varbinary](max) NULL,
[DisplayName] [nvarchar](max) NULL,
[ParametersObjectTypeName] [nvarchar](max) NULL,
CONSTRAINT [PK_dbo.ReportDataV2] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
which happens to be the Dev Express XAF Report table.
In my data context I have
public DbSet<ReportDataV2> ReportDataV2 { get; set; }
I want to be able to treat the DataTypeName field as a discriminator column without interfering with the way ReportDataV2 already works in my code.
I tried the following, but Entity Framework detects that the data structure has changed, and if I generate the migration I see that it is trying to recreate the ReportDataV2 table.
public class OrderCountReport2Configuration : EntityTypeConfiguration<ReportDataV2>
{
public OrderCountReportConfiguration()
: base()
{
ToTable("ReportDataV2", "dbo");
HasKey(tp => tp.ID);
Map<OrderCountReport>(m => m.Requires("DataTypeName").HasValue("OrderCountReport"));
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new OrderCountReportConfiguration());
base.OnModelCreating(modelBuilder);
}
}

The discriminator column must have a size limit , just like an indexed column needs a size limit

Related

TypeORM + TypeScript: migrations are not registered on migration table

I am using TypeORM in a Node.js project with ts-node and MySQL. The dev platform is on Windows.
I can perform a complete CRUD on my data, but when I try to do a migration, it doesn't work as expected.
I configured my package.json as shown on TypeORM site:
"scripts": {
...
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js" }
I configured my ormconfig.json as this:
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "m3user",
"password": "***",
"database": "m3db",
"synchronize": false,
"logging": false,
"entities": [
"src/**/models/*.ts"
]
}
When I run:
npm run typeorm -- migration:generate -n PostRefactoring
I see my migration file, which is correct (at least in the MySQL part):
import {MigrationInterface, QueryRunner} from "typeorm";
export class PostRefactoring1634588948104 implements MigrationInterface {
name = 'PostRefactoring1634588948104'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE \`supply\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`netPurchaseCost\` int NULL, \`netShippingCost\` int NOT NULL DEFAULT '0', \`supplySeconds\` int NULL, \`minQtyOrder\` int NOT NULL DEFAULT '1', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`partner\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`name\` varchar(255) NOT NULL, \`businessName\` varchar(255) NOT NULL, \`address\` varchar(255) NOT NULL, \`supplyListId\` int NULL, \`stockMovementListId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`stock_movement\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`datetime\` datetime NOT NULL, \`quantity\` int NOT NULL, \`notes\` varchar(255) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`warehouse_location\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL, \`code\` varchar(255) NOT NULL, \`movementListId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`warehouse\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`item_warehouse_config\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`isManagedInWarehouse\` tinyint NOT NULL DEFAULT 1, \`safetyStock\` int NULL DEFAULT '0', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`uom\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL, \`abbreviation\` varchar(255) NOT NULL, \`itemListId\` int NULL, \`attrListId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`item\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL DEFAULT '', \`internalCode\` varchar(255) NOT NULL DEFAULT '', \`supplierItemCode\` varchar(255) NOT NULL DEFAULT '', \`producerItemCode\` varchar(255) NOT NULL DEFAULT '', \`stdAcquisitionMode\` int NOT NULL DEFAULT '0', \`notes\` varchar(255) NOT NULL DEFAULT '', \`attrListId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`item_attribute\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL, \`netProductionCost\` int NULL, \`netSellingPrice\` int NULL, \`vatPercentage\` int NULL, \`test\` varchar(255) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`attribute\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL, \`test\` varchar(255) NOT NULL, \`itemAttrListId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`user_group\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`name\` varchar(255) NOT NULL, \`isAdmin\` tinyint NOT NULL DEFAULT 0, \`isWarehouseOperator\` tinyint NOT NULL DEFAULT 0, \`isWarehouseManager\` tinyint NOT NULL DEFAULT 0, \`isMesOperator\` tinyint NOT NULL DEFAULT 0, \`isMesManager\` tinyint NOT NULL DEFAULT 0, \`isSystemOperator\` tinyint NOT NULL DEFAULT 0, \`isSystemManager\` tinyint NOT NULL DEFAULT 0, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`CREATE TABLE \`user\` (\`id\` int NOT NULL AUTO_INCREMENT, \`spare1\` varchar(255) NULL, \`spare2\` varchar(255) NULL, \`spare3\` varchar(255) NULL, \`createdBy\` varchar(255) NOT NULL, \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`updatedBy\` varchar(255) NOT NULL, \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, \`isActive\` tinyint NOT NULL DEFAULT 1, \`username\` varchar(255) NOT NULL, \`password\` varchar(255) NOT NULL, \`name\` varchar(255) NOT NULL, \`surname\` varchar(255) NOT NULL, \`email\` varchar(255) NOT NULL, \`hasAcceptedPrivacy\` tinyint NOT NULL DEFAULT 0, \`privacyAcceptanceDatetime\` datetime NULL, \`isEnabled\` tinyint NULL, \`userGroupId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
await queryRunner.query(`ALTER TABLE \`stock\` CHANGE \`ID\` \`ID\` int NOT NULL`);
await queryRunner.query(`ALTER TABLE \`stock\` DROP PRIMARY KEY`);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`ID\``);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`id\` int NOT NULL PRIMARY KEY AUTO_INCREMENT`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`spare1\` varchar(255) NULL`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`spare2\` varchar(255) NULL`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`spare3\` varchar(255) NULL`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`createdBy\` varchar(255) NOT NULL`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`createdAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`updatedBy\` varchar(255) NOT NULL`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`updatedAt\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`isActive\` tinyint NOT NULL DEFAULT 1`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`quantity\` int NOT NULL DEFAULT '0'`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`notes\` varchar(255) NULL`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`itemId\` int NULL`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`warehouseId\` int NULL`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD CONSTRAINT \`FK_623dbc561abc7fade5a85931712\` FOREIGN KEY (\`itemId\`) REFERENCES \`item\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD CONSTRAINT \`FK_2cc5be32db1259f44995d0100aa\` FOREIGN KEY (\`warehouseId\`) REFERENCES \`warehouse\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE \`partner\` ADD CONSTRAINT \`FK_0aee63b67e4a0d4501871e66ed9\` FOREIGN KEY (\`supplyListId\`) REFERENCES \`supply\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE \`partner\` ADD CONSTRAINT \`FK_c559547d5c903c0820d958b5484\` FOREIGN KEY (\`stockMovementListId\`) REFERENCES \`stock_movement\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE \`warehouse_location\` ADD CONSTRAINT \`FK_c9fec71c3c83c5209d80b0e4549\` FOREIGN KEY (\`movementListId\`) REFERENCES \`stock_movement\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE \`uom\` ADD CONSTRAINT \`FK_4135a9616440dc60342f39d80c7\` FOREIGN KEY (\`itemListId\`) REFERENCES \`item\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE \`uom\` ADD CONSTRAINT \`FK_df6b5325d8028ca9310fcccd0dd\` FOREIGN KEY (\`attrListId\`) REFERENCES \`attribute\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE \`item\` ADD CONSTRAINT \`FK_48ca3b3bc9f797f8135caa1ecfa\` FOREIGN KEY (\`attrListId\`) REFERENCES \`item_attribute\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE \`attribute\` ADD CONSTRAINT \`FK_9f5a46f694e4fe566c44f1aa9f0\` FOREIGN KEY (\`itemAttrListId\`) REFERENCES \`item_attribute\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE \`user\` ADD CONSTRAINT \`FK_2b7a243184e4f1a8b7451c09eb1\` FOREIGN KEY (\`userGroupId\`) REFERENCES \`user_group\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`user\` DROP FOREIGN KEY \`FK_2b7a243184e4f1a8b7451c09eb1\``);
await queryRunner.query(`ALTER TABLE \`attribute\` DROP FOREIGN KEY \`FK_9f5a46f694e4fe566c44f1aa9f0\``);
await queryRunner.query(`ALTER TABLE \`item\` DROP FOREIGN KEY \`FK_48ca3b3bc9f797f8135caa1ecfa\``);
await queryRunner.query(`ALTER TABLE \`uom\` DROP FOREIGN KEY \`FK_df6b5325d8028ca9310fcccd0dd\``);
await queryRunner.query(`ALTER TABLE \`uom\` DROP FOREIGN KEY \`FK_4135a9616440dc60342f39d80c7\``);
await queryRunner.query(`ALTER TABLE \`warehouse_location\` DROP FOREIGN KEY \`FK_c9fec71c3c83c5209d80b0e4549\``);
await queryRunner.query(`ALTER TABLE \`partner\` DROP FOREIGN KEY \`FK_c559547d5c903c0820d958b5484\``);
await queryRunner.query(`ALTER TABLE \`partner\` DROP FOREIGN KEY \`FK_0aee63b67e4a0d4501871e66ed9\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP FOREIGN KEY \`FK_2cc5be32db1259f44995d0100aa\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP FOREIGN KEY \`FK_623dbc561abc7fade5a85931712\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`warehouseId\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`itemId\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`notes\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`quantity\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`isActive\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`updatedAt\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`updatedBy\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`createdAt\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`createdBy\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`spare3\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`spare2\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`spare1\``);
await queryRunner.query(`ALTER TABLE \`stock\` DROP COLUMN \`id\``);
await queryRunner.query(`ALTER TABLE \`stock\` ADD \`ID\` int NOT NULL AUTO_INCREMENT`);
await queryRunner.query(`ALTER TABLE \`stock\` ADD PRIMARY KEY (\`ID\`)`);
await queryRunner.query(`ALTER TABLE \`stock\` CHANGE \`ID\` \`ID\` int NOT NULL AUTO_INCREMENT`);
await queryRunner.query(`DROP TABLE \`user\``);
await queryRunner.query(`DROP TABLE \`user_group\``);
await queryRunner.query(`DROP TABLE \`attribute\``);
await queryRunner.query(`DROP TABLE \`item_attribute\``);
await queryRunner.query(`DROP TABLE \`item\``);
await queryRunner.query(`DROP TABLE \`uom\``);
await queryRunner.query(`DROP TABLE \`item_warehouse_config\``);
await queryRunner.query(`DROP TABLE \`warehouse\``);
await queryRunner.query(`DROP TABLE \`warehouse_location\``);
await queryRunner.query(`DROP TABLE \`stock_movement\``);
await queryRunner.query(`DROP TABLE \`partner\``);
await queryRunner.query(`DROP TABLE \`supply\``);
}
But I do not see the migration in my DB. If I run:
SELECT * from migrations;
I see an empty set.
When I run:
npm run typeorm -- migration:run
The result is:
query: SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = 'm3db' AND `TABLE_NAME` = 'migrations'
query: SELECT * FROM `m3db`.`migrations` `migrations` ORDER BY `id` DESC
No migrations are pending
I checked the case-sensitivity of DB name and username.
Thanks in advance for help!
Update 1:
Running
npm run typeorm -- migration:show
I get the following result:
query: SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = 'm3db' AND `TABLE_NAME` = 'migrations'
query: SELECT * FROM `m3db`.`migrations` `migrations` ORDER BY `id` DESC
Update 2:
If I run:
npm run typeorm migration:generate -n PostRefactoring
without -- I get the following error:
Required argumento missing: n
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! backendm3#2.0.0 typeorm: `node --require ts-node/register ./node_modules/typeorm/cli.js "migration:generate" "PostRefactoring"`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the backendm3#2.0.0 typeorm script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\altth\AppData\Roaming\npm-cache\_logs\2021-10-19T20_16_05_693Z-debug.log

how to use onDelete: 'CASCADE' on TypeORM

I have 2 migrations Users and Posts and Posts has a onDelete: 'CASCADE'. For some reason when I delete a User with a Posts it throws an error saying:
"Cannot delete or update a parent row: a foreign key constraint fails (`development_db`.`posts`, CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `users` (`id`))",
but I already set my Posts entity to onDelete: 'CASCADE'. What trips me off is when I add ON DELETE CASCADE on my posts migration the cascade delete works even though I removed onDelete: 'CASCADE' on my posts model. Any idea? so what's the use of onDelete in typeorm when you can set it on migration and not on the entity but still works.
USER Migration:
/* eslint-disable class-methods-use-this */
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UsersTable1575433829516 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`
CREATE TABLE users (
id INT AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
createdAt DATETIME NOT NULL,
PRIMARY KEY(id)
);
`);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.dropTable('users', true);
}
}
POST Migration:
/* eslint-disable class-methods-use-this */
import { MigrationInterface, QueryRunner } from 'typeorm';
export class PostsTable1581617587575 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`
CREATE TABLE posts (
id INT AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
body TEXT(65000) NOT NULL,
createdAt DATETIME NOT NULL,
updatedAt DATETIME,
PRIMARY KEY(id),
userId INT NOT NULL,
FOREIGN KEY (userId)
REFERENCES users (id)
);
`);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.dropTable('posts', true);
}
}
I was running into this same error message ("Cannot delete or update a parent row: a foreign key constraint fails") because I had put the onDelete: 'CASCADE' in the #OneToMany decorator, instead of the #ManyToOne decorator, where it needed to be in order to generate the migration correctly.
Here's what didn't work for me:
#Entity()
export class User {
//...
#OneToMany(() => Post, (post) => post.user, { onDelete: 'CASCADE' })
public posts: Post[];
//...
}
Here's what worked for me:
#Entity()
export class Post {
// ...
#ManyToOne(() => User, (user) => user.posts, { onDelete: 'CASCADE' })
public user: User;
// ...
}
I'm guessing this is the issue, because based on one of your comments, it sounds like you're coming from the Rails world. In Rails, you specify the 'cascade delete' on the 'One' side (has_many :posts, dependent: :destroy). In contrast, TypeORM seems to need it on the 'Many' side.
Just add to your migration the property ON DELETE, like this !
/* eslint-disable class-methods-use-this */
import { MigrationInterface, QueryRunner } from 'typeorm';
export class PostsTable1581617587575 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`
CREATE TABLE posts (
id INT AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
body TEXT(65000) NOT NULL,
createdAt DATETIME NOT NULL,
updatedAt DATETIME,
PRIMARY KEY(id),
userId INT NOT NULL,
FOREIGN KEY (userId)
REFERENCES users (id)
ON DELETE CASCADE
);
`);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.dropTable('posts', true);
}
}

TypeORM how to seed database

I am running my Node JS backend using typeorm ORM.
Coming from Entity Framework, it was very easy to seed the db with a few lines such as
Database.SetInitializer(new DbInitializer());
Where the DbInitializer class would contain all the seeding info.
Is there a similar approach to seed the database in TypeOrm?
If not, what is the recommended way of doing it?
1) Create a new migration with the data insertion statements?
2) Create a task where you instantiate and save entities?
Unfortunately, there is no officially released solution from TypeORM (at the time this answer was being published).
But there is a nice workaround we can use:
create another connection inside ormconfig.js file and specify another
folder for "migrations" - in fact our seeds
generate and run your seeds with -c <connection name>. That's it!
Sample ormconfig.js:
module.exports = [
{
...,
migrations: [
'src/migrations/*.ts'
],
cli: {
migrationsDir: 'src/migrations',
}
},
{
name: 'seed',
...,
migrations: [
'src/seeds/*.ts'
],
cli: {
migrationsDir: 'src/seeds',
}
}
]
Sample package.json:
{
...
scripts: {
"seed:generate": "ts-node typeorm migration:generate -c seed -n ",
"seed:run": "ts-node typeorm migration:run -c seed",
"seed:revert": "ts-node typeorm migration:revert -c seed",
},
...
}
For those who are using TypeORM with Nest.js, here is a solution to perform your seeding programatically, from within your code.
Rough idea:
We create a dedicated "seeding module" containing a "seeding middleware" that is responsible for conducting the seeding and ensuring that all seeding is done before any request is answered.
For any request that arrives, the seeding middleware intercepts it and postpones it until it is confirmed that seeding is done.
If the db has been seeded, the "seeding middleware" passes the request to the next middleware.
To speed things up, the "seeding middleware" keeps a "seeding complete" flag as state in memory to avoid any further db-checks after the seeding has occurred.
Implementation:
For this to work, first create a module that registers a middleware that listens to all incoming requests:
// file: src/seeding/SeedingModule.ts
#Module({})
export class SeedingModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(SeedingMiddleware)
.forRoutes('*')
}
}
Now create the middleware:
// file: src/seeding/SeedingMiddleware.ts
import { Injectable, NestMiddleware } from '#nestjs/common';
import { Request, Response } from 'express';
import { EntityManager } from 'typeorm';
import { SeedingLogEntry } from './entities/SeedingLogEntry.entity';
#Injectable()
export class SeedingMiddleware implements NestMiddleware {
// to avoid roundtrips to db we store the info about whether
// the seeding has been completed as boolean flag in the middleware
// we use a promise to avoid concurrency cases. Concurrency cases may
// occur if other requests also trigger a seeding while it has already
// been started by the first request. The promise can be used by other
// requests to wait for the seeding to finish.
private isSeedingComplete: Promise<boolean>;
constructor(
private readonly entityManager: EntityManager,
) {}
async use(req: Request, res: Response, next: Function) {
if (await this.isSeedingComplete) {
// seeding has already taken place,
// we can short-circuit to the next middleware
return next();
}
this.isSeedingComplete = (async () => {
// for example you start with an initial seeding entry called 'initial-seeding'
// on 2019-06-27. if 'initial-seeding' already exists in db, then this
// part is skipped
if (!await this.entityManager.findOne(SeedingLogEntry, { id: 'initial-seeding' })) {
await this.entityManager.transaction(async transactionalEntityManager => {
await transactionalEntityManager.save(User, initialUsers);
await transactionalEntityManager.save(Role, initialRoles);
// persist in db that 'initial-seeding' is complete
await transactionalEntityManager.save(new SeedingLogEntry('initial-seeding'));
});
}
// now a month later on 2019-07-25 you add another seeding
// entry called 'another-seeding-round' since you want to initialize
// entities that you just created a month later
// since 'initial-seeding' already exists it is skipped but 'another-seeding-round'
// will be executed now.
if (!await this.entityManager.findOne(SeedingLogEntry, { id: 'another-seeding-round' })) {
await this.entityManager.transaction(async transactionalEntityManager => {
await transactionalEntityManager.save(MyNewEntity, initalSeedingForNewEntity);
// persist in db that 'another-seeding-round' is complete
await transactionalEntityManager.save(new SeedingLogEntry('another-seeding-round'));
});
}
return true;
})();
await this.isSeedingComplete;
next();
}
}
Finally here is the entity that we use to record in our db that a seeding of a certain type has occured. Make sure to register it as entity in your TypeOrmModule.forRoot call.
// file: src/seeding/entities/Seeding.entity.ts
import { Entity, PrimaryColumn, CreateDateColumn } from 'typeorm';
#Entity()
export class Seeding {
#PrimaryColumn()
public id: string;
#CreateDateColumn()
creationDate: Date;
constructor(id?: string) {
this.id = id;
}
}
An alternative seeding solution using lifecycle events:
with Nest.js, you can also implement the OnApplicationBootstrap interface (see lifecycle events) instead of going for a middleware-based solution to handle your seedings. The onApplicationBootstrap method will "called once the application has fully started and is bootstrapped". This approach, however, in contrast to a middleware-solution, will not allow you to seed your db in a multi-tenant environment where db-schemas for different tenants will be created at runtime and seeding needs to be conducted several times at runtime for different tenants after they are created.
I would love to see such functionality as well (and we're not alone), but at the moment, there's no official feature for seeding.
in lack of such a built-in feature, I think the next best thing would be to create a migration script named 0-Seed (so it precedes any other migration scripts you might have) and have the seed data populated there.
#bitwit has created a snippet that may come in handy for you; it's a function that reads the data from yaml files, which you can incorporate in the seed migration script.
after some research, however, I found another interesting approach: bind an after_create event to the table, and initialize the data in the listener.
I haven't implemented this, so I'm not sure it can be done directly with TypeORM.
In Nest.js, this is what B12Toaster's alternative solution using OnApplicationBootstrap could look like.
src/seeding.service.ts
import { Injectable, Logger } from '#nestjs/common';
import { EntityManager } from 'typeorm';
import { UserEntity} from 'src/entities/user.entity';
import { RoleEntity } from 'src/entities/role.entity';
import { userSeeds } from 'src/seeds/user.seeds';
import { roleSeeds } from 'src/seeds/role.seeds';
#Injectable()
export class SeedingService {
constructor(
private readonly entityManager: EntityManager,
) {}
async seed(): Promise<void> {
// Replace with your own seeds
await Promise.all([
this.entityManager.save(UserEntity, userSeeds),
this.entityManager.save(RoleEntity, roleSeeds),
]);
}
}
src/app.module.ts
import { Module, OnApplicationBootstrap } from '#nestjs/common'
import { TypeOrmModule } from '#nestjs/typeorm';
import { getConnectionOptions } from 'typeorm';
#Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: async () =>
Object.assign(await getConnectionOptions(), {
autoLoadEntities: true,
}),
}),
TypeOrmModule.forFeature([
CompanyOrmEntity,
ProductOrmEntity,
]),
],
providers: [
SeedingService,
...
],
...
})
export class AppModule implements OnApplicationBootstrap {
constructor(
private readonly seedingService: SeedingService,
) {}
async onApplicationBootstrap(): Promise<void> {
await this.seedingService.seed();
}
}
Looks like a module is being built for this, typeorm-seeding.
While using a initial migration for seeding also works its not very useful for testing where a freshly seeded DB may be needed for tests to pass. Once you start creating more migrations you can't drop, sync and run migrations without errors. This could be solved by being able to run migration:run for a single migration file but with the CLI you currently can't. My solution was a light weight script that accesses the QueryRunner object through a typeorm connection:
// testSeed.ts
import { ConnectionOptions, createConnection, QueryRunner } from "typeorm";
import { config } from "../config";
import { DevSeed } from "./DevSeed";
createConnection(config.typeOrmConfig as ConnectionOptions).then(async connection => {
let queryRunner = connection.createQueryRunner("master");
// runs all seed SQL commands in this function.
await DevSeed(queryRunner);
await queryRunner.release();
return connection.close();
});
Then run node ./dist/path/to/testSeed.js
Also for NestJS you can use the nestjs-console package to execute tasks. This way you'll have access to entities, services, repositories, etc. I like this better than the middleware solution proposed by #B12Toaster because you don't need to maintain it as production code.
Create a seed command as shown below, then simply: yarn console seed.
There's a working example here (runs in the CI): https://github.com/thisismydesign/nestjs-starter/tree/ee7abf6d481b1420708e87dea3cb99ca110cc168
Along these lines:
src/console.ts
import { BootstrapConsole } from 'nestjs-console';
import { AppModule } from 'src/server/app/app.module';
const bootstrap = new BootstrapConsole({
module: AppModule,
useDecorators: true,
});
bootstrap.init().then(async (app) => {
try {
await app.init();
await bootstrap.boot();
app.close();
process.exit(0);
} catch (e) {
app.close();
process.exit(1);
}
});
src/console/seed.service.ts
import { Inject } from '#nestjs/common';
import { Console, Command } from 'nestjs-console';
import { UsersService } from 'src/users/users.service';
#Console()
export class SeedService {
constructor(
#Inject(UsersService) private usersService: UsersService,
) {}
#Command({
command: 'seed',
description: 'Seed DB',
})
async seed(): Promise<void> {
await this.seedUsers();
}
async seedUsers() {
await this.usersService.create({ name: 'Joe' });
}
}
package.json
{
"scripts": {
"console": "ts-node -r tsconfig-paths/register src/console.ts",
I have modified #B12Toaster's answer (For seeding to the database in NestJs) to be able to take an array of objects to seed. His answer helped considerably and I was also looking for a way to make it take many DB objects at once. Here's the little modification made to the seedingMiddleware.ts
// file: src/seeding/SeedingMiddleware.ts
import { Injectable, NestMiddleware } from '#nestjs/common';
import { Request, Response } from 'express';
import { TxnCategory } from 'src/txn-categories/entities/txn-category.entity';
import { init_categories } from 'src/txn-categories/entities/txn_cat-seed-data';
import { init_services } from 'src/txn-services/entities/txn-serv-seed-data';
import { TxnService } from 'src/txn-services/entities/txn-service.entity';
import { EntityManager } from 'typeorm';
import { Seeding } from './entities/seeding.entity';
#Injectable()
export class SeedingMiddleware implements NestMiddleware {
// to avoid roundtrips to db we store the info about whether
// the seeding has been completed as boolean flag in the middleware
// we use a promise to avoid concurrency cases. Concurrency cases may
// occur if other requests also trigger a seeding while it has already
// been started by the first request. The promise can be used by other
// requests to wait for the seeding to finish.
private isSeedingComplete: Promise<boolean>;
constructor(private readonly entityManager: EntityManager) {}
async use(req: Request, res: Response, next: any) {
if (await this.isSeedingComplete) {
// seeding has already taken place,
// we can short-circuit to the next middleware
return next();
}
this.isSeedingComplete = (async () => {
// for example you start with an initial seeding entry called 'initial-seeding'
// if 'init-txn-cats' and 'init-txn-serv' already exists in db, then this
// part is skipped
// MODIFIED
if (
!(await this.entityManager.findOne(Seeding, {
id: 'init-txn-cats',
}))
) {
await this.entityManager.transaction(
async (transactionalEntityManager) => {
for (let i = 0; i < init_categories.length; i++) {
await transactionalEntityManager.save(
TxnCategory,
init_categories[i],
);
}
await transactionalEntityManager.save(new Seeding('init-txn-cats'));
},
);
}
// MODIFIED
if (
!(await this.entityManager.findOne(Seeding, {
id: 'init-txn-serv',
}))
) {
await this.entityManager.transaction(
async (transactionalEntityManager) => {
for (let i = 0; i < init_services.length; i++) {
await transactionalEntityManager.save(
TxnService,
init_services[i],
);
}
await transactionalEntityManager.save(new Seeding('init-txn-serv'));
},
);
}
return true;
})();
await this.isSeedingComplete;
next();
}
}
Then the array of DB objects referenced would be like so:
// file: src/txn-categories/entities/txn_cat-seed-data.ts
export const init_categories = [
{
id: 1,
category_name: 'name 1',
category_code: 'cat_code_1',
enabled: true,
},
{
id: 2,
category_name: 'name 2',
category_code: 'cat_code_2',
enabled: true,
},
{
id: 3,
category_name: 'name 3',
category_code: 'cat_code_3',
enabled: true,
},
// etc
];
Same format for the src/txn-services/entities/txn-serv-seed-data.ts file.
Every other thing in B12Toaster's answer remains the same hence you'll still have your module and entity files as below:
SeedingModule:
// file: src/seeding/SeedingModule.ts
#Module({})
export class SeedingModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(SeedingMiddleware)
.forRoutes('*')
}
}
SeedingEntity:
// file: src/seeding/entities/Seeding.entity.ts
import { Entity, PrimaryColumn, CreateDateColumn } from 'typeorm';
#Entity()
export class Seeding {
#PrimaryColumn()
public id: string;
#CreateDateColumn()
creationDate: Date;
constructor(id?: string) {
this.id = id;
}
}
Cheers!
The simplest and most efficient way to do it is by creating a new migration file as follows:
import { MigrationInterface, QueryRunner } from 'typeorm';
export class <Class Name> implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.connection
.createQueryBuilder()
.insert()
.into('table_name', ['columns_1','column_2',...])
.values([
{
columns_1: value,
},
{
column_2: value
}
])
.execute();
}
}
Run the following code and it will work like a charm!
so heres how I am seeding data from an sql file with insert statement.
Heres my whole migration file after adding the seed
import { MigrationInterface, QueryRunner } from 'typeorm';
import * as path from 'path';
import * as fs from 'fs';
let insertPermissionQueries = fs
.readFileSync(path.resolve(__dirname, '../../scripts/sql/insert.sql'))
.toString()
.replace(/(\r\n|\n|\r)/gm, ' ') // remove newlines
.replace(/\s+/g, ' '); // excess white space
export class init1591103087130 implements MigrationInterface {
name = 'init1591103087130';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "public"."RoleTemp" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" text, "created_on" TIMESTAMP DEFAULT now(), "is_active" boolean DEFAULT true, "role_type" text, "created_by" uuid NOT NULL, "status" text, "alias" text, "operation" text, "rejection_reason" text, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer, CONSTRAINT "UQ_835baad60041a3413f9ef95bc07" UNIQUE ("idx"), CONSTRAINT "PK_a76dd0012be252eefbdd4a2a589" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "RoleTemp_idx_key" ON "public"."RoleTemp" ("idx") `,
);
await queryRunner.query(
`CREATE TABLE "public"."PermissionRoleTemp" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "permission_base_name" text, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer NOT NULL, "permission_id" integer NOT NULL, CONSTRAINT "PK_c1f2648a18ac911e096f08c187d" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "public"."Permission" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "base_name" text NOT NULL, "url" text NOT NULL, "method" text NOT NULL, "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "permission_type" text, "alias" text NOT NULL, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "PK_28657fa560adca66b359c18b952" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "public"."PermissionRole" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "permission_base_name" text NOT NULL, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer NOT NULL, "permission_id" integer NOT NULL, CONSTRAINT "PK_b5e2271c229f65f17ee93677a0f" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "public"."UserRole" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer NOT NULL, "company_user_id" integer NOT NULL, CONSTRAINT "PK_431fc1ec3d46ac513ef3701604e" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "public"."UsersTemp" ("idx" uuid DEFAULT uuid_generate_v1(), "username" text, "first_name" text, "middle_name" text, "last_name" text, "password" text, "email" text, "address" text, "phone_number" text, "phone_ext" text, "company_idx" uuid, "is_superadmin" boolean NOT NULL DEFAULT false, "operation" text, "created_by" text, "status" text, "rejection_reason" text, "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer, "user_id" integer, CONSTRAINT "PK_9d3fbcec3cc0b054324f93da038" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "public"."Role" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" text NOT NULL, "alias" text NOT NULL, "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "role_type" text, "created_by" uuid NOT NULL, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "UQ_c9a53325a7642edb5f9bd44f5aa" UNIQUE ("idx"), CONSTRAINT "PK_422113329ddec949e76c7943c56" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "Role_idx_key" ON "public"."Role" ("idx") `,
);
await queryRunner.query(
`CREATE TABLE "public"."Users" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "username" text NOT NULL, "first_name" text NOT NULL, "middle_name" text, "last_name" text NOT NULL, "password" text NOT NULL, "email" text, "address" text, "phone_number" text, "phone_ext" text, "company_idx" uuid, "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "is_superadmin" boolean NOT NULL DEFAULT false, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer NOT NULL, CONSTRAINT "PK_ac3c96e3c912cbda773b7c7edc9" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "public"."CompanyUser" ("id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "company_idx" uuid, "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "user_id" integer, CONSTRAINT "PK_4a915d69bf079a8e5dd10784cc3" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "public"."RoleTemp" ADD CONSTRAINT "FK_d304588d17c9349ca6e7ebee5d3" FOREIGN KEY ("role_id") REFERENCES "public"."Role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "public"."PermissionRoleTemp" ADD CONSTRAINT "FK_7e7cdde853500f56b3db43fc258" FOREIGN KEY ("role_id") REFERENCES "public"."RoleTemp"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "public"."PermissionRoleTemp" ADD CONSTRAINT "FK_0068d3de1c59050561d35f17544" FOREIGN KEY ("permission_id") REFERENCES "public"."Permission"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "public"."PermissionRole" ADD CONSTRAINT "FK_5b57492441a568bc7562fbbaa5b" FOREIGN KEY ("role_id") REFERENCES "public"."Role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "public"."PermissionRole" ADD CONSTRAINT "FK_1951a810af06342fcd4530ec61c" FOREIGN KEY ("permission_id") REFERENCES "public"."Permission"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "public"."UserRole" ADD CONSTRAINT "FK_fb09d73b0dd011be81a272e1efa" FOREIGN KEY ("role_id") REFERENCES "public"."Role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "public"."UserRole" ADD CONSTRAINT "FK_b221977a41587e58d7c58e16db0" FOREIGN KEY ("company_user_id") REFERENCES "public"."CompanyUser"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "public"."UsersTemp" ADD CONSTRAINT "FK_6d74dfaddaa94e1bba0c8c12a2f" FOREIGN KEY ("role_id") REFERENCES "public"."Role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "public"."UsersTemp" ADD CONSTRAINT "FK_e5b2930fe35042dab17945bb131" FOREIGN KEY ("user_id") REFERENCES "public"."Users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "public"."Users" ADD CONSTRAINT "FK_34be125e29cee0e71d58456aed7" FOREIGN KEY ("role_id") REFERENCES "public"."Role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "public"."CompanyUser" ADD CONSTRAINT "FK_1354e3e408b5ffdebe476a6fbd2" FOREIGN KEY ("user_id") REFERENCES "public"."Users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(insertPermissionQueries);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "public"."CompanyUser" DROP CONSTRAINT "FK_1354e3e408b5ffdebe476a6fbd2"`,
);
await queryRunner.query(
`ALTER TABLE "public"."Users" DROP CONSTRAINT "FK_34be125e29cee0e71d58456aed7"`,
);
await queryRunner.query(
`ALTER TABLE "public"."UsersTemp" DROP CONSTRAINT "FK_e5b2930fe35042dab17945bb131"`,
);
await queryRunner.query(
`ALTER TABLE "public"."UsersTemp" DROP CONSTRAINT "FK_6d74dfaddaa94e1bba0c8c12a2f"`,
);
await queryRunner.query(
`ALTER TABLE "public"."UserRole" DROP CONSTRAINT "FK_b221977a41587e58d7c58e16db0"`,
);
await queryRunner.query(
`ALTER TABLE "public"."UserRole" DROP CONSTRAINT "FK_fb09d73b0dd011be81a272e1efa"`,
);
await queryRunner.query(
`ALTER TABLE "public"."PermissionRole" DROP CONSTRAINT "FK_1951a810af06342fcd4530ec61c"`,
);
await queryRunner.query(
`ALTER TABLE "public"."PermissionRole" DROP CONSTRAINT "FK_5b57492441a568bc7562fbbaa5b"`,
);
await queryRunner.query(
`ALTER TABLE "public"."PermissionRoleTemp" DROP CONSTRAINT "FK_0068d3de1c59050561d35f17544"`,
);
await queryRunner.query(
`ALTER TABLE "public"."PermissionRoleTemp" DROP CONSTRAINT "FK_7e7cdde853500f56b3db43fc258"`,
);
await queryRunner.query(
`ALTER TABLE "public"."RoleTemp" DROP CONSTRAINT "FK_d304588d17c9349ca6e7ebee5d3"`,
);
await queryRunner.query(`DROP TABLE "public"."CompanyUser"`);
await queryRunner.query(`DROP TABLE "public"."Users"`);
await queryRunner.query(`DROP INDEX "public"."Role_idx_key"`);
await queryRunner.query(`DROP TABLE "public"."Role"`);
await queryRunner.query(`DROP TABLE "public"."UsersTemp"`);
await queryRunner.query(`DROP TABLE "public"."UserRole"`);
await queryRunner.query(`DROP TABLE "public"."PermissionRole"`);
await queryRunner.query(`DROP TABLE "public"."Permission"`);
await queryRunner.query(`DROP TABLE "public"."PermissionRoleTemp"`);
await queryRunner.query(`DROP INDEX "public"."RoleTemp_idx_key"`);
await queryRunner.query(`DROP TABLE "public"."RoleTemp"`);
}
}
I used a simpler method with migrations here is my code i believe it should be simpler so run this in your migrations
import { MigrationInterface, QueryRunner } from 'typeorm';
const tableName = 'foo';
const columnName = 'foo_column';
const features = ['foo_content_1', 'foo_content_2'];
export class seedIntoPermissionsTable1638518166717 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await Promise.all(features.map((feature) => queryRunner.query(`INSERT INTO ${tableName} (${columnName}) VALUES ('${feature}')`)));
}
public async down(queryRunner: QueryRunner): Promise<void> {
await Promise.all(features.map((feature) => queryRunner.query(`DELETE FROM ${tableName} WHERE ${columnName}='${feature}';`)));
}
}
This is what i tend to use for my seeders.
You can also give https://github.com/tada5hi/typeorm-extension a try for populating the database with (fake) data.

Grails domain-classes mapping in one-to-one relation

I have ready database table schema and I need use it in my Grails app.
My tables in PostgreSQL:
CREATE TABLE "user" (
id serial NOT NULL,
login character varying(32) NOT NULL,
password character varying(32) NOT NULL,
email character varying(255) NOT NULL,
date_created time with time zone NOT NULL DEFAULT now(),
last_updated time with time zone NOT NULL DEFAULT now(),
is_banned boolean DEFAULT false,
CONSTRAINT "PK_user_id" PRIMARY KEY (id),
CONSTRAINT "UN_user_email" UNIQUE (email),
CONSTRAINT "UN_user_login" UNIQUE (login)
)
CREATE TABLE profile (
"user" integer NOT NULL DEFAULT nextval('profile_id_seq'::regclass),
first_name character varying(25) NOT NULL,
middle_name character varying(25) NOT NULL,
last_name character varying(25) NOT NULL,
address integer,
CONSTRAINT "PK_PROFILE_user" PRIMARY KEY ("user"),
CONSTRAINT "FK_PROFILE_user_USER_id" FOREIGN KEY ("user")
REFERENCES "user" (id) MATCH SIMPLE
ON UPDATE RESTRICT ON DELETE CASCADE
)
As you can see, "profile" table has primary key, which is its foreign key too. This is main "feature" with which the problems with geails mapping.
My implementation of tables mapping to grails domain classes:
class User {
...
static hasOne = [profile : Profile];
...
}
class Profile {
...
User user;
...
static mapping = {
id name: 'user'
version false
address column: 'address'
user column: '`user`'
};
...
}
This class mapping crash with exception:
Invocation of init method failed; nested exception is org.hibernate.MappingException: Could not determine type for: ru.redlisa.model.User, at table: profile, for columns: [org.hibernate.mapping.Column(user)]
How to correctly map the tables to grails domain-classes?
How to get interaction interface?
like this:
User user = new User();
user.addToProdile(new Profile());
Or
new User(profile: new Profile()).save();
You could try to use this approach:
class User {
...
Profile profile
...
static mapping = {
id column: 'user', generator: 'foreign', params: [ property: 'profile']
...
}
}
Big thanks to araxn1d for showing right way with foreign generator. I've rewrite my domains like this:
class User {
...
static hasOne = [profile : Profile];
...
}
class Profile {
...
static belongsTo = [address: Address,
user: User];
...
static mapping = {
id column: '`user`', generator: 'foreign', params: [ property: 'user']
version false
address column: 'address'
user column: '`user`', insertable: false, updateable: false
};
...
}
and it works!

What's the proper way to update an nhibernate entity from a asp.net POST action method?

I'm new to nHibernate, and trying to get my head around the proper way to update detached objects from a web application form POST. (We're using ASP.NET MVC)
The object I'm trying to update contains (among other things) an IList of child objects, mapped something like this:
<bag name="PlannedSlices" inverse="true" cascade="all-delete-orphan">
<key column="JobNumber" />
<one-to-many class="SliceClass" />
</bag>
We have arranged our MVC edit view form so that when it's posted back, our action method is passed am object (incluing the List<> of child items. We round-trip all the entity ID's correctly via the form.
Our naive attempt at the post action method does a session.SaveOrUpdate(parentObject), with the parentObject which has been scraped from view form by the default modelbinder.
This seems to work fine for any of the following scenarios:
Creating a new parent object
Modifying the parent's properties
Adding new child objects
Modifying existing child objects
(Looking at nHibernate logs, I can see it correctly establishing if the objects are new or existing, and issuing the appropriate UPDATE or INSERT)
The scenario which fails is:
- Deleting child objects - i.e if they're not in the IList, they don't get deleted from the database. There's no exception or anything, they just don't get deleted.
My understanding is that this is because the magic which nHibernate performs to create a list of children which require deletion doesn't work with detached instances.
I have not been able to find a simple example of what this sort of action method should look like with nHibernate (i.e. using a model-binder object as a detached nHibernate instance) - examples based on MS EF (e.g. http://stephenwalther.com/blog/archive/2009/02/27/chapter-5-understanding-models.aspx) seem to use a method 'ApplyPropertyChanges' to copy changed properties from the model-bound object to a re-loaded entity instance.
So, after all that, the question is pretty simple - if I have the model binder give me a new object which contains collections of child objects, how should I update that via nHibernate, (where 'update' includes possibly deletion of children)?
Here's an example that does what I think you're trying to do. Let me know if I've misunderstood what you're trying to do.
Given the following "domain" classes:
public class Person
{
private IList<Pet> pets;
protected Person()
{ }
public Person(string name)
{
Name = name;
pets = new List<Pet>();
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual IEnumerable<Pet> Pets
{
get { return pets; }
}
public virtual void AddPet(Pet pet)
{
pets.Add(pet);
}
public virtual void RemovePet(Pet pet)
{
pets.Remove(pet);
}
}
public class Pet
{
protected Pet()
{ }
public Pet(string name)
{
Name = name;
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
With the following mapping:
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
LazyLoad();
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name);
HasMany(x => x.Pets)
.Cascade.AllDeleteOrphan()
.Access.AsLowerCaseField()
.SetAttribute("lazy", "false");
}
}
public class PetMap : ClassMap<Pet>
{
public PetMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name);
}
}
This test:
[Test]
public void CanDeleteChildren()
{
Person person = new Person("joe");
Pet dog = new Pet("dog");
Pet cat = new Pet("cat");
person.AddPet(dog);
person.AddPet(cat);
Repository.Save(person);
UnitOfWork.Commit();
CreateSession();
UnitOfWork.BeginTransaction();
Person retrievedPerson = Repository.Get<Person>(person.Id);
Repository.Evict(retrievedPerson);
retrievedPerson.Name = "Evicted";
Assert.AreEqual(2, retrievedPerson.Pets.Count());
retrievedPerson.RemovePet(retrievedPerson.Pets.First());
Assert.AreEqual(1, retrievedPerson.Pets.Count());
Repository.Save(retrievedPerson);
UnitOfWork.Commit();
CreateSession();
UnitOfWork.BeginTransaction();
retrievedPerson = Repository.Get<Person>(person.Id);
Assert.AreEqual(1, retrievedPerson.Pets.Count());
}
runs and generates the following sql:
DeletingChildrenOfEvictedObject.CanDeleteChildren : Passed
NHibernate: INSERT INTO [Person] (Name, Id) VALUES (#p0, #p1); #p0 = 'joe', #p1 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
NHibernate: INSERT INTO [Pet] (Name, Id) VALUES (#p0, #p1); #p0 = 'dog', #p1 = '464e59c7-74d0-4317-9c22-9bf801013abb'
NHibernate: INSERT INTO [Pet] (Name, Id) VALUES (#p0, #p1); #p0 = 'cat', #p1 = '010c2fd9-59c4-4e66-94fb-9bf801013abb'
NHibernate: UPDATE [Pet] SET Person_id = #p0 WHERE Id = #p1; #p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2', #p1 = '464e59c7-74d0-4317-9c22-9bf801013abb'
NHibernate: UPDATE [Pet] SET Person_id = #p0 WHERE Id = #p1; #p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2', #p1 = '010c2fd9-59c4-4e66-94fb-9bf801013abb'
NHibernate: SELECT person0_.Id as Id5_0_, person0_.Name as Name5_0_ FROM [Person] person0_ WHERE person0_.Id=#p0; #p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
NHibernate: SELECT pets0_.Person_id as Person3_1_, pets0_.Id as Id1_, pets0_.Id as Id6_0_, pets0_.Name as Name6_0_ FROM [Pet] pets0_ WHERE pets0_.Person_id=#p0; #p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
NHibernate: UPDATE [Person] SET Name = #p0 WHERE Id = #p1; #p0 = 'Evicted', #p1 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
NHibernate: UPDATE [Pet] SET Name = #p0 WHERE Id = #p1; #p0 = 'dog', #p1 = '464e59c7-74d0-4317-9c22-9bf801013abb'
NHibernate: UPDATE [Pet] SET Person_id = null WHERE Person_id = #p0 AND Id = #p1; #p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2', #p1 = '010c2fd9-59c4-4e66-94fb-9bf801013abb'
NHibernate: DELETE FROM [Pet] WHERE Id = #p0; #p0 = '010c2fd9-59c4-4e66-94fb-9bf801013abb'
NHibernate: SELECT person0_.Id as Id5_0_, person0_.Name as Name5_0_ FROM [Person] person0_ WHERE person0_.Id=#p0; #p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
NHibernate: SELECT pets0_.Person_id as Person3_1_, pets0_.Id as Id1_, pets0_.Id as Id6_0_, pets0_.Name as Name6_0_ FROM [Pet] pets0_ WHERE pets0_.Person_id=#p0; #p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
Note the DELETE FROM [Pet]...
so, what you need to be able to do is hand nhibernate a Person object (in this example) with the modified collections and it should be able to determmine what to delete. Make sure you have the Cascade.AllDeleteOrphan() attribute set.
Rob's answer convinced me to look more closely at the 'load the existing item into the new session and then merge' approach, and of course there's ISession.Merge, which appears to do exactly what I wanted, which is to take a fresh object and merge it with it's predecessor who's just been reloaded into the second session.
So I think the answer to the question I tried to ask is "reload the existing entity and then call 'ISession.Merge' with the new entity."

Resources