4

We are trying to generate a non-nullable rowversion column on SQL Server with EF Core 3.1 using Fluent API:

public class Person
{
    public int Id { get; set; }
    public byte[] Timestamp { get; set; }
}

public class PersonEntityConfiguration : IEntityTypeConfiguration<Person>
{
    public void Configure(EntityTypeBuilder<Person> builder)
    {
        builder.HasKey(p => p.Id);

        builder.Property(p => p.Timestamp)
            .IsRowVersion()
            .IsRequired();
    }
}

This works fine when the entire table is new:

public partial class PersonMigration : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Persons",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                Timestamp = table.Column<byte[]>(rowVersion: true, nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Persons", x => x.Id);
            });
    }
}

However, we sometimes need to add the rowversion to an existing table. In that case, EF Core generates an invalid migration:

public partial class PersonTimestampMigration : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AddColumn<byte[]>(
            name: "Timestamp",
            table: "Persons",
            rowVersion: true,
            nullable: false,
            defaultValue: new byte[] {  });
    }
}

The default value generated above will cause an exception when being applied to the database:

Failed executing DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']

ALTER TABLE [Persons] ADD [Timestamp] rowversion NOT NULL DEFAULT 0x;

Microsoft.Data.SqlClient.SqlException (0x80131904): Defaults cannot be created on columns of data type timestamp. Table 'Persons', column 'Timestamp'.
Could not create constraint or index. See previous errors.

Is this a known bug in EF Core? The issue can be fixed by manually removing the defaultValue: new byte[] { } from the migration, but is there a way of suppressing the default value from being generated using the Fluent API?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
14207973
  • 489
  • 6
  • 19
  • The `ROWVERSION` data type in SQL Server is always handled by the database engine - therefore, I believe you **cannot** define a default for it - what for, anyway? What's the perceived benefit? This is **not** an EF Core bug - it's a **SQL Server feature** .. – marc_s Sep 02 '20 at 11:42
  • 1
    @marc_s: The bug is that EF Core shouldn't be generating the default constraint, since it knows that the value is always populated by the database. `IsRowVersion` configures the property as `ValueGeneratedOnAddOrUpdate` and `IsConcurrencyToken`. – 14207973 Sep 02 '20 at 11:47
  • OK - that's a valid point - but as you say yourself - since **YOU** also know that - just don't specify a default value in your model ! Also on a side note: naming this column `Timestamp` is a bit dangerous and not advisable - after all, that's still a reserved T-SQL keyword..... try to avoid any potential conflicts in your naming, if you ever can ! – marc_s Sep 02 '20 at 11:50
  • 2
    I'm not specifying a default. EF Core incorrectly generates it for me when I configure a property with `.IsRowVersion().IsRequired()` (on an existing table). It would solve my issue if there was a way of suppressing this default. Fair point re column name. – 14207973 Sep 02 '20 at 11:54

1 Answers1

4

Is this a known bug in EF Core?

It is bug/defect for sure, but probably not known, since it's happening even in the latest at this time EF Core 5.0 preview. Or is known, but with low priority (for them) - you have to check EF Core Issue Tracker.

Tried adding explicitly .HasDefaultValue(null) and .HasDefaultValueSql(null) - nothing helps, so the only option is manually removing the defaultValue: new byte[] { } from the migration. The good thing is that when you do so, it works and the column is created and populated successfully even though the table has existing records (which is the reason EF Core adds such defaultValue argument for new required columns in general, but as we see shouldn't do that for ROWVERSION).

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343