Mysql
 sql >> Datenbank >  >> RDS >> Mysql

So erstellen Sie CreatedOn und UpdatedOn mit EF Core 2.1 und Pomelo

Problem:

Ich habe dies auf (was zu sein scheint) einen Fehler in Pomelo eingegrenzt. Problem ist hier:

https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues /801

Das Problem ist, dass Pomelo einen defaultValue erstellt Eigenschaft für DateTime und andere Strukturen beim Generieren der Migration. Wenn bei der Migration ein Standardwert festgelegt wird, wird die Wertgenerierungsstrategie außer Kraft gesetzt, und die SQL sieht dann falsch aus.

Die Problemumgehung besteht darin, die Migration zu generieren und dann die Migrationsdatei manuell zu ändern, um den defaultValue festzulegen auf null (oder die gesamte Zeile entfernen).

Ändern Sie beispielsweise Folgendes:

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

Dazu:

migrationBuilder.AddColumn<DateTime>(
                name: "UpdatedTime",
                table: "SomeTable",
                nullable: false)
                .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn);

Das Migrationsskript spuckt dann das richtige SQL mit DEFAULT CURRENT_TIMESTAMP aus für TIMESTAMP . Wenn Sie die [Column(TypeName = "TIMESTAMP")] -Attribut, wird ein datetime(6) verwendet Spalte und spucke DEFAULT CURRENT_TIMESTAMP(6) aus .

LÖSUNG:

Ich habe mir einen Workaround ausgedacht, der die Erstellte Zeit (wird nur bei INSERT von der Datenbank aktualisiert) und die Aktualisierte Zeit (wird nur bei INSERT und UPDATE von der Datenbank aktualisiert) korrekt implementiert.

Definieren Sie zuerst Ihre Entität wie folgt:

public class SomeEntity
{
    // Other properties here ...

    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
}

Fügen Sie dann Folgendes zu OnModelCreating() hinzu :

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

Dies führt zu einer perfekten anfänglichen Migration (wobei migrationBuilder.CreateTable verwendet wird) und generiert das erwartete 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),

Das sollte auch an Migrationen arbeiten, die vorhandene Tabellen aktualisieren, aber stellen Sie sicher, dass defaultValue ist immer null.

Das SetBeforeSaveBehavior und SetAfterSaveBehavior Zeilen verhindern, dass EF jemals versucht, die Erstellte Zeit mit einem Standardwert zu überschreiben. Dadurch werden die Spalten „Erstellt“ und „Aktualisiert“ effektiv aus Sicht von EF schreibgeschützt, sodass die Datenbank die gesamte Arbeit erledigen kann.

Sie können dies sogar in eine Schnittstelle und eine Erweiterungsmethode extrahieren:

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

Implementieren Sie dann die Schnittstelle für alle Ihre zeitgestempelten Entitäten:

public class SomeEntity : ITimestampedEntity
{
    // Other properties here ...

    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
}

Dadurch können Sie die Entität innerhalb von OnModelCreating() einrichten so:

protected override void OnModelCreating(ModelBuilder builder)
{
    // Other model creating stuff here ...

    builder.Entity<SomeTimestampedEntity>().UseTimestampedProperty();
}