Problema:
L'ho ristretto a (quello che sembra essere) un bug in Pomelo. Il problema è qui:
https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues /801
Il problema è che Pomelo crea un defaultValue
proprietà per DateTime
e altre strutture durante la generazione della migrazione. Se un valore predefinito è impostato sulla migrazione, sovrascrive la strategia di generazione del valore e quindi l'SQL non sembra corretto.
La soluzione alternativa consiste nel generare la migrazione e quindi modificare manualmente il file delle migrazioni per impostare il defaultValue
a null
(o rimuovi l'intera riga).
Ad esempio, cambia questo:
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);
A questo:
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedTime",
table: "SomeTable",
nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn);
Lo script di migrazione sputerà quindi l'SQL corretto con DEFAULT CURRENT_TIMESTAMP
per TIMESTAMP
. Se rimuovi il [Column(TypeName = "TIMESTAMP")]
attributo, utilizzerà un datetime(6)
colonna e sputa DEFAULT CURRENT_TIMESTAMP(6)
.
SOLUZIONE:
Ho trovato una soluzione alternativa che implementa correttamente il tempo creato (aggiornato dal database solo su INSERT) e il tempo aggiornato (aggiornato dal database solo su INSERT e UPDATE).
Innanzitutto, definisci la tua entità in questo modo:
public class SomeEntity
{
// Other properties here ...
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}
Quindi, aggiungi quanto segue a 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);
}
Ciò produce una migrazione iniziale perfetta (dove migrationBuilder.CreateTable
viene utilizzato) e genera l'SQL previsto:
`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),
Questo dovrebbe funziona anche su migrazioni che aggiornano le tabelle esistenti, ma assicurati che defaultValue
è sempre nullo.
Il SetBeforeSaveBehavior
e SetAfterSaveBehavior
le righe impediscono a EF di provare a sovrascrivere l'ora di creazione con un valore predefinito. Rende effettivamente le colonne Created e Updated lette solo dal punto di vista di EF, consentendo al database di fare tutto il lavoro.
Puoi anche estrarlo in un'interfaccia e in un metodo di estensione:
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;
}
Quindi implementa l'interfaccia su tutte le tue entità con timestamp:
public class SomeEntity : ITimestampedEntity
{
// Other properties here ...
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}
Ciò ti consente di impostare l'entità da OnModelCreating()
così:
protected override void OnModelCreating(ModelBuilder builder)
{
// Other model creating stuff here ...
builder.Entity<SomeTimestampedEntity>().UseTimestampedProperty();
}