Mysql
 sql >> Database >  >> RDS >> Mysql

Come creare CreatedOn e UpdatedOn usando EF Core 2.1 e Pomelo

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();
}