Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Como criar CreatedOn e UpdatedOn usando EF Core 2.1 e Pomelo


Problema:

Eu reduzi isso a (o que parece ser) um bug no Pomelo. A questão está aqui:

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

O problema é que o Pomelo cria um defaultValue propriedade para DateTime e outras estruturas ao gerar a migração. Se um valor padrão for definido na migração, ele substituirá a estratégia de geração de valor e o SQL parecerá incorreto.

A solução alternativa é gerar a migração e, em seguida, modificar manualmente o arquivo de migração para definir o defaultValue para null (ou remova a linha inteira).

Por exemplo, altere isso:
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);

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

O script de migração então exibirá o SQL correto com DEFAULT CURRENT_TIMESTAMP para TIMESTAMP . Se você remover o [Column(TypeName = "TIMESTAMP")] atributo, ele usará um datetime(6) coluna e cuspir DEFAULT CURRENT_TIMESTAMP(6) .

SOLUÇÃO:

Eu criei uma solução alternativa que implementa corretamente o Tempo de Criação (atualizado pelo banco de dados somente em INSERT) e o Tempo de Atualização (atualizado pelo banco de dados apenas em INSERT e UPDATE).

Primeiro, defina sua entidade assim:
public class SomeEntity
{
    // Other properties here ...

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

Em seguida, adicione o seguinte 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);
}

Isso produz uma migração inicial perfeita (onde migrationBuilder.CreateTable é usado) e gera o 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),

Isso deve também funcionam em migrações que atualizam tabelas existentes, mas certifique-se de que defaultValue é sempre nulo.

O SetBeforeSaveBehavior e SetAfterSaveBehavior As linhas impedem que o EF tente sobrescrever a hora de Criado por um valor padrão. Ele efetivamente faz com que as colunas Criadas e Atualizadas sejam lidas apenas do ponto de vista do EF, permitindo que o banco de dados faça todo o trabalho.

Você pode até extrair isso em uma interface e método de extensão:
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;
}

Em seguida, implemente a interface em todas as suas entidades com carimbo de data/hora:
public class SomeEntity : ITimestampedEntity
{
    // Other properties here ...

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

Isso permite que você configure a Entidade de dentro de OnModelCreating() igual a:
protected override void OnModelCreating(ModelBuilder builder)
{
    // Other model creating stuff here ...

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