EF Core 6 how to migrate entity property from int to long, while keeping database type as int?

Viewed 802

I have a .Net 6 app using EF Core 6. We are using migrations to create change scripts for the database (sql server), which means that the code is not invoking migrations itself, but we do manual database updates with script generated with EF migrations .

I have a scenario where I would like to update an entity property from 32 bit int to 64 bit long. I would also like to change the corresponding data base column from int to bigint, but I want to do this in 2 steps:

  1. first update the code and deploy and run on un-changed database.
  2. once the updated code is deployed and running then I want to update the data base, so that the database column is changed from int to bigint.

I naively thought this was simple to do be just changing the C# property type from int to long without updating the database, because I assumed that the 32 bit integer value could just be converted automatically to a 64 bit integer. However, I got an exception with this message:

"Unable to cast object of type 'System.Int32' to type 'System.Int64'."

I am sure there are good reasons for this, but the question is if this can be done in 2 steps somehow, maybe by some EF core model configuration stuff, so that the code updated to use a long can be safely deployed without updating the database at the same time, or do I simply need to updating code and database at the same time (which means taking down the app, deploy new code, run db update script and then start the app again) ?

2 Answers

Temporarily use two properties: One to be accessed from code and one to be mapped by EF:

public class Model
{
    [Column("Prop")]
    public int TempPropForEF { get; set; }  // Mapped to table

    [NotMapped]
    public long Prop                        // Used in code
    {                     
        get => TempPropForEF;
        set => TempPropForEF = (int)value;
    }
}

You can give any name to the mapping property and then specify the column name to be mapped to explicitly. Of course you can also use the fluent API to configure the model instead of attributes.

If you use the fluent API, you can dynamically swap the two properties once the DB was updated by mapping Prop and un-mapping TempPropForEF. something like this:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    If (IsOldVersion()) {
        modelBuilder.Entity<Model>().Ignore(m => m.Prop);
        modelBuilder.Entity<Model>().Property(b => b.TempPropForEF).HasColumnName("Prop");
    } else { // new version
        modelBuilder.Entity<Model>().Ignore(m => m.TempPropForEF);
    }
}

See:

Better to do this using a Value Converter,

Value converters allow property values to be converted when reading from or writing to the database. This conversion can be from one value to another of the same type (for example, encrypting strings) or from a value of one type to a value of another type (for example, converting enum values to and from strings in the database.)

eg

If (IsOldVersion()) 
{
    modelBuilder.Entity<Model>().Property(e => e.Prop).HasConversion<int>();
} 
Related