I decided to change a column from non-nullable to a nullable one. No issue here. I changed the property to have "?" in the type (DateTime
=> DateTime?
) and the migration generated is OK.
Though, I want to change the value considered as "null" to a real null. Meaning, changing the value 0001-01-01 00:00:00
to null
.
I created an extension method that properly generated the SQL code for up and down. I executed this after the column change in Up
and before in Down
.
The issue comes when executing the Up
because the SQL from my extension method is executed before the changes of the column alteration is applied.
Is there anyway to force changes to be apply before executing this statement? (See FIXME
in the code where the code could be inserted. Two locations possible.)
Extension class
public static class MigrationsCorrections
{
public static OperationBuilder<SqlOperation> ChangeDateToNullable(this MigrationBuilder migrationBuilder, string table, string column)
{
//FIXME: Either having a call here this to force previous changes to be applied
return migrationBuilder.Sql($"UPDATE {table} SET {column} = null WHERE {column} = '0001-01-01 00:00:00';");
}
public static OperationBuilder<SqlOperation> ChangeDateToNotNullable(this MigrationBuilder migrationBuilder, string table, string column)
{
return migrationBuilder.Sql($"UPDATE {table} SET {column} = '0001-01-01 00:00:00' WHERE {column} = null;");
}
}
Migration class
// IMPORTATION OF THE EXTENSION CLASS
public partial class MetaPersonBirthDateNullable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "BirthDay",
table: "MetaPersons",
type: "TEXT",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "TEXT");
//FIXME: Either having a call here this to force previous changes to be applied
migrationBuilder.ChangeDateToNullable("MetaPersons", "BirthDay");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.ChangeDateToNotNullable("MetaPersons", "BirthDay");
migrationBuilder.AlterColumn<DateTime>(
name: "BirthDay",
table: "MetaPersons",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
oldClrType: typeof(DateTime),
oldType: "TEXT",
oldNullable: true);
}
}
SQL generated by migration
BEGIN TRANSACTION;
UPDATE MetaPersons SET BirthDay = null WHERE BirthDay = '0001-01-01 00:00:00';
CREATE TABLE "ef_temp_MetaPersons" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_MetaPersons" PRIMARY KEY AUTOINCREMENT,
"BirthDay" TEXT NULL,
"DeathDay" TEXT NULL,
"ExternalId" TEXT NULL,
"MetaSource" TEXT NULL,
"Name" TEXT COLLATE NOCASE NULL,
"Professions" TEXT NULL
);
INSERT INTO "ef_temp_MetaPersons" ("Id", "BirthDay", "DeathDay", "ExternalId", "MetaSource", "Name", "Professions")
SELECT "Id", "BirthDay", "DeathDay", "ExternalId", "MetaSource", "Name", "Professions"
FROM "MetaPersons";
COMMIT;
PRAGMA foreign_keys = 0;
BEGIN TRANSACTION;
DROP TABLE "MetaPersons";
ALTER TABLE "ef_temp_MetaPersons" RENAME TO "MetaPersons";
COMMIT;
PRAGMA foreign_keys = 1;
BEGIN TRANSACTION;
CREATE UNIQUE INDEX "IX_MetaPersons_ExternalId_MetaSource" ON "MetaPersons" ("ExternalId", "MetaSource");
CREATE INDEX "IX_MetaPersons_Name" ON "MetaPersons" ("Name");
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20220309145323_MetaPerson.BirthDate Nullable', '6.0.2');
COMMIT;
As we can see, the UPDATE
statement is not in the proper order
EDIT 1
I tried using a MigrationOperation class instead as per Microsoft Custom Migration, but I get...
An operation of type 'ChangeDateToNullable' will be attempted while a rebuild of table 'MetaPersons' is pending. The database may not be in an expected state. Review the SQL generated by this migration to help diagnose any failures. Consider moving these operations to a subsequent migration.
This particular solution is one I thought, having a second migration, but that's not what I would prefer as those changes are linked together.
CodePudding user response:
From the documentation Using MigrationBuilder.Sql():
Use the EXEC function when a statement must be the first or only one in a SQL batch. It might also be needed to work around parser errors in idempotent migration scripts that can occur when referenced columns don't currently exist on a table.
So better create two migrations, one for ALTER another for UPDATE.
CodePudding user response:
I fixed the issue pursuing in the direction of MigrationOperations. I overwrite the Generate method of IMigrationsSqlGenerator
.
For the sake of having a generalized solution, I created a new Database project that includes this new MigrationsSqlGenerator
with the classes needed to change the date column value from/to null. I also added two classes to execute any SQL code that respect the order and thus enables changing the value of a column after altering it. And all this, in the same migration.
See https://github.com/djon2003/com.cyberinternauts.csharp.Database for the complete solution.
And https://www.codeproject.com/Articles/5327089/Executing-SQL-code-within-EntityFrameworkCore-migr for the related article.