sql >> Base de Datos >  >> RDS >> Mysql

Cómo crear CreatedOn y UpdatedOn usando EF Core 2.1 y Pomelo

Problema:

He reducido esto a (lo que parece ser) un error en Pomelo. El problema está aquí:

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

El problema es que Pomelo crea un defaultValue propiedad para DateTime y otras estructuras al generar la migración. Si se establece un valor predeterminado en la migración, anula la estrategia de generación de valor y el SQL parece incorrecto.

La solución es generar la migración y luego modificar manualmente el archivo de migraciones para establecer el defaultValue a null (o eliminar toda la línea).

Por ejemplo, cambia esto:

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 esto:

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

El script de migración luego escupirá el SQL correcto con DEFAULT CURRENT_TIMESTAMP para TIMESTAMP . Si elimina el [Column(TypeName = "TIMESTAMP")] atributo, utilizará un datetime(6) columna y escupe DEFAULT CURRENT_TIMESTAMP(6) .

SOLUCIÓN:

Se me ocurrió una solución alternativa que implementa correctamente la hora creada (actualizada por la base de datos solo en INSERTAR) y la hora actualizada (actualizada por la base de datos solo en INSERTAR y ACTUALIZAR).

Primero, define tu entidad así:

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

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

Luego, agregue lo siguiente 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);
}

Esto produce una migración inicial perfecta (donde migrationBuilder.CreateTable se utiliza) y genera el SQL esperado:

`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),

Esto debería también trabaje en migraciones que actualicen tablas existentes, pero asegúrese de que defaultValue siempre es nulo.

El SetBeforeSaveBehavior y SetAfterSaveBehavior Las líneas evitan que EF intente sobrescribir la hora de creación con un valor predeterminado. Hace que las columnas Creado y Actualizado sean de solo lectura desde el punto de vista de EF, lo que permite que la base de datos haga todo el trabajo.

Incluso puede extraer esto en una interfaz y un método de extensión:

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

Luego implemente la interfaz en todas sus entidades con marca de tiempo:

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

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

Esto le permite configurar la Entidad desde dentro de OnModelCreating() así:

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

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