EF Core 5 migrations - update composite primary and foreign keys - entity-framework-core

I am using EF Core code first migrations. I have two tables (models), TableA has 4 columns which are set as composite primary key and TableB has the same 4 columns set as primary key and foreign key to TableA.
On of the four columns is of type int and I need to change it to long. I changed the type to long in both models and added a new migration. However, I am getting the following error when trying to update the database to apply the migration:
The object 'PK_TableA' is dependent on column 'Number'.
The object 'FK_TableB_TableA_Column1_Column2_Column3_Column4' is dependent on column 'Number'.
ALTER TABLE ALTER COLUMN Number failed because one or more objects access this column.
The column Number (column #3) in the error above is the one which has the type of int.
The generated migration is as follows:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<long>(
name: "Number",
table: "TableA",
type: "bigint",
nullable: false,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AlterColumn<long>(
name: "Number",
table: "TableB",
type: "bigint",
nullable: false,
oldClrType: typeof(int),
oldType: "int");
}
I checked a number of topics here but none of them includes a solution for a "composite primary and foreign keys".
I found this article about manually changing the generated migration code. But I am not sure if it s going to work with a composite primary and foreign key. Or, if it is the right way to fix this issue.
Any advise will be appreciated.

Just in case someone was looking for an answer to this question, this is how I solved the issue.
I edited the generated migration code, as follows, to manually remove the foreign key first in TableB and then the two composite primary keys in TableA and TableB and then added them after changing the column type to bigint (according to the generated migration code):
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_TableB",
table: "TableB");
migrationBuilder.DropPrimaryKey(
name: "PK_TableA",
table: "TableA");
migrationBuilder.DropPrimaryKey(
name: "PK_TableB",
table: "TableB");
migrationBuilder.AlterColumn<long>(
name: "Number",
table: "TableA",
type: "bigint",
nullable: false,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AlterColumn<long>(
name: "Number",
table: "TableB",
type: "bigint",
nullable: false,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AddPrimaryKey(
name: "PK_TableA",
table: "TableA",
columns: new[] { "Column1", "Column2", "Column3", "Column4" });
migrationBuilder.AddPrimaryKey(
name: "PK_TableB",
table: "TableB",
columns: new[] { "Column1", "Column2", "Column3", "Column4" });
migrationBuilder.AddForeignKey(
name: "FK_TableB",
table: "TableB",
columns: new[] { "Column1", "Column2", "Column3", "Column4" },
principalTable: "TableA",
principalColumns: new[] { "Column1", "Column2", "Column3", "Column4" },
onDelete: ReferentialAction.Cascade);
}
The same code was added for the Down method.

Related

Postgres - cannot use subquery in DEFAULT expression

A new unique column is being added to a table. This column's value comes from a C# random code generator method, but for existing rows I need to rely on Postgres.
I have the following EF Core migration:
protected override void Up(MigrationBuilder migrationBuilder)
{
var randomCodeSql = #"
SELECT string_agg (substr('ABCDEFGHJKLMNPQRSTUVWXYZ123456789', ceil (random() * 33)::integer, 1), '')
FROM generate_series(1, 6)";
migrationBuilder.AddColumn<string>(
name: "reference_code",
table: "invitations",
type: "character varying(12)",
maxLength: 12,
nullable: false,
defaultValueSql: randomCodeSql);
migrationBuilder.CreateIndex(
name: "ix_invitations_reference_code",
table: "invitations",
column: "reference_code",
unique: true);
}
The raw SQL is from this answer.
I get this error:
0A000: cannot use subquery in DEFAULT expression
Is there a code-first solution to this problem?

typeorm relation in embedded column

I'm working with NestJs, Typeorm and Postgresql.
I'm trying to use ManyToOne relation in embedded entity. I need to load foreign key column in node environment, so added one additional column(createdById column below). This makes problem.
Here is my code.
A.ts
#Entity()
export class A {
#PrimaryGeneratedColumn()
id!: number;
#Column(() => Embed, { prefix: false })
embed!: Embed;
#CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
}
Embed.ts
export class Embed {
#Column()
x!: number;
#Column()
y!: number;
#ManyToOne(() => B)
#JoinColumn({ name: 'created_by_id' })
createdBy?: B;
#Column({ name: 'created_by_id' })
createdById!: number;
}
B.ts
#Entity()
export class B {
#PrimaryGeneratedColumn()
id!: number;
#CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
}
When I run the app with option TYPEORM_SYNCHRONIZE=true and TYPEORM_LOGGING=true, I get error messages like query failed: CREATE TABLE "a" ("id" SERIAL NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "created_by_id" integer NOT NULL, "created_by_id" integer NOT NULL, "x" integer NOT NULL, "y" integer NOT NULL, CONSTRAINT "PK_684f21444e543375e4c2e6f27fe" PRIMARY KEY ("id")), Message: column \"created_by_id\" specified more than once.. Typeorm trying to create created_by_id column twice. (I applied custom NamingStrategy so that column of embedded entity's name to be snake_case)
If I place createdBy and createdById column to A directly, then it makes no error. Is it problem of typeorm version? Or any other solutions?
package version:
"dependencies": {
"#nestjs/typeorm": "7.1.0,
"typeorm": "0.2.31",
}
run with docker container,
node image: 16.14.2-alpine3.15,
postgres image: mdillon/postgis:11-alpine
TypeORM docs say you don't need to decorate embed class with #Entity().
See Name class in https://orkhan.gitbook.io/typeorm/docs/embedded-entities

EF Core migration taking old migrations changes along with new columns

I have added 2 more columns in aspnetusers table and added migrations. But it's failing and showing error stating FK_Projects_AspNetUsers_UserId' is not a constraint.
Could not drop constraint. See previous errors.
public string NavbarBackGroundImagePath { get; set; }
public string NavbarBackGroundColorCode { get; set; }
Added these 2 columns.
migrationBuilder.DropForeignKey(
name: "FK_Projects_AspNetUsers_UserId",
table: "Projects");
migrationBuilder.DropIndex(
name: "IX_Projects_UserId",
table: "Projects");
migrationBuilder.DropColumn(
name: "UserId",
table: "Projects");
migrationBuilder.AddColumn<int>(
name: "WorkspaceId",
table: "Projects",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "NavbarBackGroundColorCode",
table: "AspNetUsers",
maxLength: 10,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "NavbarBackGroundImagePath",
table: "AspNetUsers",
maxLength: 200,
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Projects_WorkspaceId",
table: "Projects",
column: "WorkspaceId");
migrationBuilder.AddForeignKey(
name: "FK_Projects_Workspaces_WorkspaceId",
table: "Projects",
column: "WorkspaceId",
principalTable: "Workspaces",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
But in migration file it's taking all these things.
According to the error message, it seems that this error happens when you want to drop the foreign key constraint in the Projects table, if the Projects table doesn't contain the "FK_Projects_AspNetUsers_UserId" constraint or doesn't contain the "UserId" property, it will show this error.
So, please check the Projects table in the database, whether you have removed the UerId column. And whether the Projects table have the foreign key constraint with the AspNetUsers table. If the Projects table doesn't contain this constraint, try to remove the DropForeignKey related code:
migrationBuilder.DropForeignKey(
name: "FK_Projects_AspNetUsers_UserId",
table: "Projects");

Sequelize upsert throws unique violation for schema with nullable unique column

I'm facing an unique constraint violation issue when doing an upsert, because the UPDATE query built by sequelize ignores the partial index constraint defined by the model (unless it doesn't matter). I'm new to node+sequelize so I might be missing something obvious, but I went through all the potential places for finding the appropriate answers, inclusive of the sequelize code, but I'm not able to find the answer I'm looking for. Really appreciate your help!
My current versions:
"pg": "7.9.0",
"sequelize": "5.21.3"
I have a model that consists of a primary key: id and two other unique indexes of which one of them is a nullable field.
module.exports.Entities = sequelize.define('entities', {
id: {type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, allowNull: false, primaryKey: true},
cId: {type: Sequelize.STRING, allowNull: false},
pId: {type: Sequelize.UUID, allowNull: false},
eKey: {type: Sequelize.INTEGER, allowNull: true}
}, {
indexes: [
{
name: 'unique_c_id_p_id',
fields: ['c_id', 'p_id'],
unique: true
},
{
name: 'unique_e_key',
fields: ['e_key'],
unique: true,
where: {
eKey: {
[Op.not]: null
}
}
}
]
})
and the table itself looks like below:
CREATE TABLE public.entities (
id UUID DEFAULT uuid_generate_v4 (),
c_id UUID NOT NULL,
p_id UUID NOT NULL,
e_key INTEGER DEFAULT NULL,
CONSTRAINT ENTITY_SERVICE_PKEY PRIMARY KEY (id),
CONSTRAINT unique_c_id_p_id UNIQUE (c_id, p_id)
);
CREATE UNIQUE INDEX unique_e_key ON public.entities (e_key) WHERE e_key IS NOT NULL;
The upsert method call looks like:
module.exports.upsert = async (Model, values) => Model.upsert(values, {returning: true})
I pass the above Entities model, and the below value as arguments to this function.
{
"id"="3169d4e2-8e2d-451e-8be0-40c0b28e2aa9",
"c_id"="00000000-0000-0000-0000-000000000000",
"p_id"="78bce392-4a15-4a8a-986b-c9398787345f",
"e_key"= null
}
Issue: SequelizeUniqueConstraintError
Sequelize tries to do an insert followed by an update query when we attempt to update an existing record using the upsert method.
The insert query shows a conflict, since the record exists already, and sequelize upsert call proceeds on to invoke the update query.
However, the query that it builds to UPDATE looks something like below:
"SQL statement UPDATE entities SET id='3169d4e2-8e2d-451e-8be0-40c0b28e2aa9',c_id='00000000-0000-0000-0000-000000000000',p_id='78bce392-4a15-4a8a-986b-c9398787345f',e_key=NULL
WHERE (id = '3169d4e2-8e2d-451e-8be0-40c0b28e2aa9'
OR e_key IS NULL
OR (c_id = '00000000-0000-0000-0000-000000000000' AND p_id = '78bce392-4a15-4a8a-986b-c9398787345f'))
RETURNING id\nPL/pgSQL function pg_temp_5.sequelize_upsert() line 1 at SQL statement"
Now, I do understand the reason why it's throwing the unique constraint violation, since in the above query's WHERE clause sequelize calls OR e_key IS NULL since e_key = null and that could potentially return more than 1 record, and the SET is trying to update the same value for all those records that were returned thereby violating the primaryKey constraints, unique constraints etc.
What I would like to understand is that:
Why does sequelize not exclude the e_key unique constraint based on the partial index defined given that it picks the WHERE clause attributes based on the constraints defined in the Model & it's indexes?
Is there anything that I could do to get past this issue?
Or, am I missing something obvious that I could fix and try?
Really appreciate you taking your time to read and respond. Thanks!

How to avoid DROP DEFAULT statements with Doctrine 2 Migrations diff on first run?

I had an existing PostgreSQL database with a table created like this:
CREATE TABLE product (id SERIAL PRIMARY KEY, name VARCHAR(100) DEFAULT NULL)
This table is described in a YML Doctrine2 file within a Symfony2 project:
Acme\DemoBundle\Entity\Product:
type: entity
table: product
fields:
id:
id: true
type: integer
nullable: false
generator:
strategy: SEQUENCE
name:
type: string
length: 100
nullable: true
When I run for the first time the Doctrine Migrations diff task, I should get a versioning file with no data in the up and down methods. But what I get instead is this :
// ...
class Version20120807125808 extends AbstractMigration
{
public function up(Schema $schema)
{
// this up() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "postgresql");
$this->addSql("ALTER TABLE product ALTER id DROP DEFAULT");
}
public function down(Schema $schema)
{
// this down() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "postgresql");
$this->addSql("CREATE SEQUENCE product_id_seq");
$this->addSql("SELECT setval('product_id_seq', (SELECT MAX(id) FROM product))");
$this->addSql("ALTER TABLE product ALTER id SET DEFAULT nextval('product_id_seq')");
}
}
Why are differences detected? How can I avoid this? I tried several sequence strategies with no success.
A little update on this question.
Using Doctrine 2.4, the solution is to use the IDENTITY generator strategy :
Acme\DemoBundle\Entity\Product:
type: entity
table: product
id:
type: integer
generator:
strategy: IDENTITY
fields:
name:
type: string
length: 100
nullable: true
To avoid DROP DEFAULT on fields that have a default value in the database, the default option on the field is the way to go. Of course this can be done with lifecycle callbacks, but it's necessary to keep the default value in the database if this database is used by other apps.
For a "DEFAULT NOW()" like default value, the solution is the following one:
Acme\DemoBundle\Entity\Product:
type: entity
table: product
id:
type: integer
generator:
strategy: IDENTITY
fields:
creation_date:
type: datetime
nullable: false
options:
default: CURRENT_TIMESTAMP
Doctrine 2.0 does not support the SQL DEFAULT keyword, and will always try to drop a postgres default value.
I have found no solution to this problem, I just let doctrine handle the sequences itself.
This is a opened bug registered here :
http://www.doctrine-project.org/jira/browse/DBAL-903