Проблема:
Я сузил это до (как представляется) ошибки в Помело.Проблема здесь:
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 будет исправлена.