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