Как создать CreatedOn и updatedOn с помощью EF Core 2.1 и Pomelo - PullRequest
0 голосов
/ 12 июня 2018

В подходе Code First, как определить мою сущность так, чтобы:

  • CreatedOn NOT NULL - значение при вставке генерировалось БД с текущей отметкой времени
  • Updated NULL - значение генерируется при обновлении БД с текущей отметкой времени

Пример объекта:

public class MyEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column(TypeName = "TIMESTAMP")]
    public DateTime CreatedOn { get; set; }
    [Column(TypeName = "TIMESTAMP")]
    public DateTime UpdatedOn { get; set; }
}

DbContext:

public class MyContext : DbContext
{
    public MyContext(DbContextOptions options) : base(options) {}

    public DbSet<MyEntity> Entities { get; set; }
}

Конечный результатв базе данных должно быть:

  • CreatedOn NOT NULL - не имеет Extra - по умолчанию может быть CURRENT_TIMESTAMP
  • UpdatedOn NULL - дополнительно при обновлении CURRENT_TIMESTAMP - нет по умолчанию или по умолчанию -NULL

1 Ответ

0 голосов
/ 16 июля 2019

Проблема:

Я сузил это до (как представляется) ошибки в Помело.Проблема здесь:

https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/801

Проблема в том, что Pomelo создает свойство defaultValue для DateTime и другие структуры при генерации миграции.Если для миграции задано значение по умолчанию, оно переопределяет стратегию генерации значений, а затем SQL выглядит некорректно.

Временное решение: создать миграцию, а затем вручную изменить файл миграции, чтобы установить * 1014.* на null (или удалить всю строку).

Например, измените это:

migrationBuilder.AddColumn<DateTime>(
                name: "UpdatedTime",
                table: "SomeTable",
                nullable: false,
                defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)))
                .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn);

На это:

migrationBuilder.AddColumn<DateTime>(
                name: "UpdatedTime",
                table: "SomeTable",
                nullable: false)
                .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn);

Скрипт миграции будетзатем выплюните правильный SQL с DEFAULT CURRENT_TIMESTAMP для TIMESTAMP.Если вы удалите атрибут [Column(TypeName = "TIMESTAMP")], он будет использовать столбец datetime(6) и выплюнет DEFAULT CURRENT_TIMESTAMP(6).

РЕШЕНИЕ:

Я придумалОбходной путь, который правильно реализует Время создания (обновляется базой данных только при INSERT) и Время обновления (обновляется базой данных только при INSERT и UPDATE).

Сначала определите свою сущность следующим образом:

public class SomeEntity
{
    // Other properties here ...

    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
}

Затем добавьте следующее к OnModelCreating():

protected override void OnModelCreating(ModelBuilder builder)
{
    // Other model creating stuff here ...

    builder.Entity<SomeEntity>.Property(d => d.CreatedTime).ValueGeneratedOnAdd();
    builder.Entity<SomeEntity>.Property(d => d.UpdatedTime).ValueGeneratedOnAddOrUpdate();

    builder.Entity<SomeEntity>.Property(d => d.CreatedTime).Metadata.BeforeSaveBehavior = PropertySaveBehavior.Ignore;
    builder.Entity<SomeEntity>.Property(d => d.CreatedTime).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
    builder.Entity<SomeEntity>.Property(d => d.UpdatedTime).Metadata.BeforeSaveBehavior = PropertySaveBehavior.Ignore;
    builder.Entity<SomeEntity>.Property(d => d.UpdatedTime).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
}

Это обеспечивает идеальную начальную миграцию (где используется migrationBuilder.CreateTable) и генерирует ожидаемый SQL:

`created_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`updated_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),

Этот должен также работать с миграциями, которые обновляют существующие таблицы, но убедитесь, что defaultValue всегда равен нулю.

Строки PropertySaveBehavior не позволяют EF пытаться перезаписатьВремя создания со значением по умолчанию.Это эффективно делает столбцы «Создано» и «Обновлено» только для чтения с точки зрения EF, позволяя базе данных выполнять всю работу.

Вы даже можете извлечь это в интерфейс и метод расширения:

public interface ITimestampedEntity
    {
        DateTime CreatedTime { get; set; }
        DateTime UpdatedTime { get; set; }
    }
public static EntityTypeBuilder<TEntity> UseTimestampedProperty<TEntity>(this EntityTypeBuilder<TEntity> entity) where TEntity : class, ITimestampedEntity
{
    entity.Property(d => d.CreatedTime).ValueGeneratedOnAdd();
    entity.Property(d => d.UpdatedTime).ValueGeneratedOnAddOrUpdate();

    entity.Property(d => d.CreatedTime).Metadata.BeforeSaveBehavior = PropertySaveBehavior.Ignore;
    entity.Property(d => d.CreatedTime).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
    entity.Property(d => d.UpdatedTime).Metadata.BeforeSaveBehavior = PropertySaveBehavior.Ignore;
    entity.Property(d => d.UpdatedTime).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;

    return entity;
}

Затем реализуйте интерфейс для всех ваших сущностей с метками времени:

public class SomeEntity : ITimestampedEntity
{
    // Other properties here ...

    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
}

Это позволяет вам настроить сущность из OnModelCreating() следующим образом:

protected override void OnModelCreating(ModelBuilder builder)
{
    // Other model creating stuff here ...

    builder.Entity<SomeTimestampedEntity>().UseTimestampedProperty();
}

Это также будет работать с DateTimeOffset после того, как проблема 799 будет исправлена.

...