Problem:
Zawęziłem to do (co wydaje się być) błędem w Pomelo. Problem jest tutaj:
https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues /801
Problem polega na tym, że Pomelo tworzy defaultValue
właściwość DateTime
i inne struktury podczas generowania migracji. Jeśli w migracji ustawiona jest wartość domyślna, zastępuje ona strategię generowania wartości, a kod SQL wygląda wtedy niepoprawnie.
Rozwiązaniem jest wygenerowanie migracji, a następnie ręczne zmodyfikowanie pliku migracji, aby ustawić defaultValue
na null
(lub usuń całą linię).
Na przykład zmień to:
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);
Do tego:
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedTime",
table: "SomeTable",
nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn);
Skrypt migracji wyrzuci wtedy poprawny kod SQL z DEFAULT CURRENT_TIMESTAMP
dla TIMESTAMP
. Jeśli usuniesz [Column(TypeName = "TIMESTAMP")]
atrybut, użyje datetime(6)
kolumna i wypluć DEFAULT CURRENT_TIMESTAMP(6)
.
ROZWIĄZANIE:
Wymyśliłem obejście, które poprawnie implementuje Utworzony czas (aktualizowany przez bazę danych tylko przy INSERT) i Zaktualizowany czas (aktualizowany przez bazę danych tylko przy INSERT i UPDATE).
Najpierw zdefiniuj swoją jednostkę w następujący sposób:
public class SomeEntity
{
// Other properties here ...
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}
Następnie dodaj następujące elementy do 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.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore);
builder.Entity<SomeEntity>.Property(d => d.CreatedTime).Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
builder.Entity<SomeEntity>.Property(d => d.UpdatedTime).Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore);
builder.Entity<SomeEntity>.Property(d => d.UpdatedTime).Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
}
Daje to idealną migrację początkową (gdzie migrationBuilder.CreateTable
jest używany) i generuje oczekiwany kod 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),
To powinno pracuj również nad migracjami, które aktualizują istniejące tabele, ale upewnij się, że defaultValue
jest zawsze pusta.
SetBeforeSaveBehavior
i SetAfterSaveBehavior
linie uniemożliwiają EF próbowanie zastąpienia czasu utworzenia wartością domyślną. Skutecznie sprawia, że kolumny Utworzone i Zaktualizowane są odczytywane tylko z punktu widzenia EF, umożliwiając bazie danych wykonanie całej pracy.
Możesz nawet wyodrębnić to do interfejsu i metody rozszerzenia:
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).SetBeforeSaveBehavior(PropertySaveBehavior.Ignore);
entity.Property(d => d.CreatedTime).SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
entity.Property(d => d.UpdatedTime).SetBeforeSaveBehavior(PropertySaveBehavior.Ignore);
entity.Property(d => d.UpdatedTime).SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
return entity;
}
Następnie zaimplementuj interfejs we wszystkich swoich jednostkach oznaczonych znacznikiem czasu:
public class SomeEntity : ITimestampedEntity
{
// Other properties here ...
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}
Pozwala to na skonfigurowanie encji z poziomu OnModelCreating()
tak:
protected override void OnModelCreating(ModelBuilder builder)
{
// Other model creating stuff here ...
builder.Entity<SomeTimestampedEntity>().UseTimestampedProperty();
}