Problém:
Zúžil jsem to na (co se zdá být) chyba v Pomelo. Problém je zde:
https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues /801
Problém je v tom, že Pomelo vytváří defaultValue
vlastnost pro DateTime
a další struktury při generování migrace. Pokud je při migraci nastavena výchozí hodnota, přepíše strategii generování hodnoty a SQL pak vypadá nesprávně.
Řešením je vygenerovat migraci a poté ručně upravit soubor migrace tak, aby nastavil defaultValue
na null
(nebo odstraňte celý řádek).
Změňte například toto:
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);
K tomu:
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedTime",
table: "SomeTable",
nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn);
Migrační skript pak vyplivne správný SQL s DEFAULT CURRENT_TIMESTAMP
pro TIMESTAMP
. Pokud odeberete [Column(TypeName = "TIMESTAMP")]
atribut, bude používat datetime(6)
sloupec a vyplivněte DEFAULT CURRENT_TIMESTAMP(6)
.
ŘEŠENÍ:
Přišel jsem s řešením, které správně implementuje Created Time (aktualizovaný databází pouze po INSERT) a Aktualizovaný čas (aktualizovaný databází pouze po INSERT a UPDATE).
Nejprve definujte svou entitu takto:
public class SomeEntity
{
// Other properties here ...
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}
Potom přidejte následující 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);
}
To vytváří perfektní počáteční migraci (kde migrationBuilder.CreateTable
se používá) a vygeneruje očekávané 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),
Toto by mělo pracovat také na migracích, které aktualizují existující tabulky, ale ujistěte se, že defaultValue
je vždy null.
SetBeforeSaveBehavior
a SetAfterSaveBehavior
řádky brání EF v tom, aby se někdy pokusil přepsat čas vytvoření výchozí hodnotou. Efektivně umožňuje, aby sloupce Created a Updated byly čteny pouze z pohledu EF, což umožňuje databázi dělat veškerou práci.
Můžete to dokonce extrahovat do metody rozhraní a rozšíření:
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;
}
Poté implementujte rozhraní do všech vašich entit s časovým razítkem:
public class SomeEntity : ITimestampedEntity
{
// Other properties here ...
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}
To vám umožní nastavit Entitu z OnModelCreating()
takhle:
protected override void OnModelCreating(ModelBuilder builder)
{
// Other model creating stuff here ...
builder.Entity<SomeTimestampedEntity>().UseTimestampedProperty();
}