npgsql / efcore.pg

Entity Framework Core provider for PostgreSQL
PostgreSQL License
1.54k stars 226 forks source link

Migration not applying after upgrading to .NET 5 #1600

Closed danielbecroft closed 3 years ago

danielbecroft commented 3 years ago

We have an application that was started under .NET Core 3.0, and has had a number of migrations generated and applied.

For some of our models, we added "shadow properties" like the below, that we added for automatic tracking of auditing and the like. One of the example fields is the CreatedOn field:

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<Post>().Property<DateTime>("CreatedOn").HasDefaultValueSql("now()");
}

After adding this field, we changed this from DateTime to DateTimeOffset, and the migration was generated as:

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.AlterColumn<DateTimeOffset>(
        name: "CreatedOn",
        table: "Posts",
        nullable: false,
        defaultValueSql: "now()",
        oldClrType: typeof(DateTime),
        oldDefaultValueSql: "now()");
}

This migration correctly applies under .NET Core 3.1. However, when running the migration under .NET 5, the statement is a no-op. The migration is recorded as run during the .Migrate() phase, but nothing else happens. The datatype of the field is not changed.

NOTE: This matches the committed version of our migration. Attempting to replicate this issue, the migration includes an oldType: "timestamp without time zone" line. If I include this line, both versions correctly apply the migration. Removing this line causes .NET 5 to not apply the migration, but .NET Core 3.1 does.

Running dotnet ef migrations script between the two migrations gives the following:

.NET Core 3.1

ALTER TABLE "Posts" ALTER COLUMN "CreatedOn" TYPE timestamp with time zone;
ALTER TABLE "Posts" ALTER COLUMN "CreatedOn" SET NOT NULL;
ALTER TABLE "Posts" ALTER COLUMN "CreatedOn" SET DEFAULT (now());

INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20201207050840_ChangeCreatedOnPropertyToDateTimeOffset', '3.0.0');

.NET 5

START TRANSACTION;

INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20201207050840_ChangeCreatedOnPropertyToDateTimeOffset', '5.0.0');

COMMIT;

Was there a fix where a previous version would not include the oldType line in the migration? (trying to work out why this line would not have been included in the original migration)? Is this a bug where we have a migration that no longer applies between the major versions?

I've setup a basic repository with the example migrations here.

AmadeoDecay commented 3 years ago

I don't know if its same issue, or something else. After updating, there is pending changes in every migrations…


protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AlterColumn<int>(
                name: "Id",
                table: "AspNetUserClaims",
                type: "int",
                nullable: false,
                oldClrType: typeof(int),
                oldType: "int")
                .Annotation("SqlServer:Identity", "1, 1");

            migrationBuilder.AlterColumn<int>(
                name: "Id",
                table: "AspNetRoleClaims",
                type: "int",
                nullable: false,
                oldClrType: typeof(int),
                oldType: "int")
                .Annotation("SqlServer:Identity", "1, 1");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AlterColumn<int>(
                name: "Id",
                table: "AspNetUserClaims",
                type: "int",
                nullable: false,
                oldClrType: typeof(int),
                oldType: "int")
                .OldAnnotation("SqlServer:Identity", "1, 1");

            migrationBuilder.AlterColumn<int>(
                name: "Id",
                table: "AspNetRoleClaims",
                type: "int",
                nullable: false,
                oldClrType: typeof(int),
                oldType: "int")
                .OldAnnotation("SqlServer:Identity", "1, 1");
        }`
roji commented 3 years ago

@AmadeoDecay are you referencing the PostgreSQL provider (or another provider) in addition to SQL Server? If so, this could be a dup of https://github.com/dotnet/efcore/issues/23456.

Otherwise, can you please open a new issue (as this doesn't seem to be related to the original issue), and include a repro project?

AmadeoDecay commented 3 years ago

@roji Thank you. I've read thread you posted and I think that is it. I have installed packages and tried to use Postgres, but failed, so I let packages there and continued to use SQL Server. And with changes described there should be it. But it generates interesting issue. Using Sql server, when I create custom identity (Guid Ids for most tables in identity) and install npg package. It creates this migration above with this code. services .AddIdentity<ApplicationUser, ApplicationRole>(options => options.SignIn.RequireConfirmedAccount = false) .AddEntityFrameworkStores<DbContext>() .AddDefaultTokenProviders() .AddUserStore<UserStore<ApplicationUser, ApplicationRole, DbContext, Guid>>() .AddRoleStore<RoleStore<ApplicationRole, DbContext, Guid>>();

roji commented 3 years ago

@AmadeoDecay I'm not sure that is related to the above problem... If you're stuck on this, can you please open a separate issue with a full code sample?

roji commented 3 years ago

@danielbecroft sorry for not commenting on this sooner...

I've tried generating migrations with EF Core 5.0, 3.1 and 3.0:

EF Core 3.0 ```c# protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AlterColumn( name: "CreatedOn", table: "Blogs", nullable: false, defaultValueSql: "now()", oldClrType: typeof(DateTime), oldType: "timestamp without time zone", oldDefaultValueSql: "now()"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.AlterColumn( name: "CreatedOn", table: "Blogs", type: "timestamp without time zone", nullable: false, defaultValueSql: "now()", oldClrType: typeof(DateTimeOffset), oldDefaultValueSql: "now()"); } ```
EF Core 3.1 ```c# protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AlterColumn( name: "CreatedOn", table: "Blogs", nullable: false, defaultValueSql: "now()", oldClrType: typeof(DateTime), oldType: "timestamp without time zone", oldDefaultValueSql: "now()"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.AlterColumn( name: "CreatedOn", table: "Blogs", type: "timestamp without time zone", nullable: false, defaultValueSql: "now()", oldClrType: typeof(DateTimeOffset), oldDefaultValueSql: "now()"); } ```
EF Core 5.0 ```c# protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AlterColumn( name: "CreatedOn", table: "Blogs", type: "timestamp with time zone", nullable: false, defaultValueSql: "now()", oldClrType: typeof(DateTime), oldType: "timestamp without time zone", oldDefaultValueSql: "now()"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.AlterColumn( name: "CreatedOn", table: "Blogs", type: "timestamp without time zone", nullable: false, defaultValueSql: "now()", oldClrType: typeof(DateTimeOffset), oldType: "timestamp with time zone", oldDefaultValueSql: "now()"); } ```

I can consistently see oldType in the Up migrations, but it's indeed missing in the Down migration generated for EF Core 3.1. Your code fragment above shows an Up migration - is it possible that this is a migration generated before EF Core 3.0?

danielbecroft commented 3 years ago

Hi @roji , These migrations were created under EF Core 3 (I can't recall if they were 3.0 or 3.1).

roji commented 3 years ago

Closing as per https://github.com/npgsql/efcore.pg/issues/1663#issuecomment-795288364