Efcore: Column Created Time in EFMigrationsHistory

Created on 17 Oct 2018  路  4Comments  路  Source: dotnet/efcore

Can I add a column Time to the table __EFMigrationsHistory with a utc time of the created row populated.
As I can see at the moment there is a hard-coded HistoryRow which cannot be changed outside.

Any ideas are very welcome.

See the following example
```C#
public class CustomHistoryRepository : NpgsqlHistoryRepository
{
public CustomHistoryRepository(HistoryRepositoryDependencies dependencies)
: base(dependencies)
{
}

    protected override void ConfigureTable(EntityTypeBuilder<HistoryRow> history)
    {
        base.ConfigureTable(history);

        history
            .Property<DateTime>("Time")
            .HasValueGenerator<DateTimeUtcValueGenerator>()
            .ValueGeneratedOnAdd();
    }

    public override string GetInsertScript(HistoryRow row)
    {
        return base.GetInsertScript(row);
    }
}

```C#
var dbOptions = new DbContextOptionsBuilder(options)
                    .UseNpgsql(settings.DefaultConnection)
                    .ReplaceService<IHistoryRepository, CustomHistoryRepository>()
                    .Options;

Further technical details

EF Core version: Microsoft.AspNetCore.Identity.EntityFrameworkCore v.2.1.3
Database Provider: Npgsql.EntityFrameworkCore.PostgreSQL v.2.1.2
Operating system: Visual Studio 2017 Community v.15.8.7

closed-question customer-reported

Most helpful comment

The entity type is currently only used for generating the CREATE TABLE statement. All other configuration is ignored.

You could generate the value in the database using a default value:

history
    .Property<DateTime>("Time")
    .HasDefaultValueSql("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'");

Or you could manually change the INSERT statement. Note however, that this would do funny things when generating a SQL script form the migrations. The value would be the time you generated the script, not the time you actually applied the migration.

public override string GetInsertScript(HistoryRow row)
{
    var stringMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string));
    var dateTimeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(DateTime));

    return @"INSERT INTO ")
        .Append(SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema))
        .Append(" (")
        .Append(SqlGenerationHelper.DelimitIdentifier(MigrationIdColumnName))
        .Append(", ")
        .Append(SqlGenerationHelper.DelimitIdentifier(ProductVersionColumnName))
        .Append(", ")
        .Append(SqlGenerationHelper.DelimitIdentifier("Time"))
        .AppendLine(")")
        .Append("VALUES (")
        .Append(stringMapping.GenerateSqlLiteral(row.MigrationId))
        .Append(", ")
        .Append(stringMapping.GenerateSqlLiteral(row.ProductVersion))
        .Append(", ")
        .Append(dateTimeMapping.GenerateSqlLiteral(DateTime.UtcNow))
        .Append(")")
        .AppendLine(SqlGenerationHelper.StatementTerminator)
        .ToString();
}

All 4 comments

@bricelam to respond

The entity type is currently only used for generating the CREATE TABLE statement. All other configuration is ignored.

You could generate the value in the database using a default value:

history
    .Property<DateTime>("Time")
    .HasDefaultValueSql("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'");

Or you could manually change the INSERT statement. Note however, that this would do funny things when generating a SQL script form the migrations. The value would be the time you generated the script, not the time you actually applied the migration.

public override string GetInsertScript(HistoryRow row)
{
    var stringMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string));
    var dateTimeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(DateTime));

    return @"INSERT INTO ")
        .Append(SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema))
        .Append(" (")
        .Append(SqlGenerationHelper.DelimitIdentifier(MigrationIdColumnName))
        .Append(", ")
        .Append(SqlGenerationHelper.DelimitIdentifier(ProductVersionColumnName))
        .Append(", ")
        .Append(SqlGenerationHelper.DelimitIdentifier("Time"))
        .AppendLine(")")
        .Append("VALUES (")
        .Append(stringMapping.GenerateSqlLiteral(row.MigrationId))
        .Append(", ")
        .Append(stringMapping.GenerateSqlLiteral(row.ProductVersion))
        .Append(", ")
        .Append(dateTimeMapping.GenerateSqlLiteral(DateTime.UtcNow))
        .Append(")")
        .AppendLine(SqlGenerationHelper.StatementTerminator)
        .ToString();
}

Your code may also just work after we implement #2737.

Thanks Brice. Your solution with HasDefaultValueSql works fine. Did't know about HasDefaultValueSql as it was different namespace I had to add.

Was this page helpful?
0 / 5 - 0 ratings