Problème :
J'ai réduit cela à (ce qui semble être) un bogue dans Pomelo. Le problème est ici :
https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues /801
Le problème est que Pomelo crée une defaultValue
propriété pour DateTime
et d'autres structures lors de la génération de la migration. Si une valeur par défaut est définie lors de la migration, elle remplace la stratégie de génération de valeur et le SQL semble alors incorrect.
La solution consiste à générer la migration, puis à modifier manuellement le fichier de migrations pour définir la defaultValue
à null
(ou supprimez toute la ligne).
Par exemple, changez ceci :
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);
À ceci :
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedTime",
table: "SomeTable",
nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn);
Le script de migration crachera alors le bon SQL avec DEFAULT CURRENT_TIMESTAMP
pour TIMESTAMP
. Si vous supprimez le [Column(TypeName = "TIMESTAMP")]
attribut, il utilisera un datetime(6)
colonne et cracher DEFAULT CURRENT_TIMESTAMP(6)
.
SOLUTION :
J'ai trouvé une solution de contournement qui implémente correctement l'heure créée (mise à jour par la base de données uniquement sur INSERT) et l'heure mise à jour (mise à jour par la base de données uniquement sur INSERT et UPDATE).
Tout d'abord, définissez votre entité comme suit :
public class SomeEntity
{
// Other properties here ...
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}
Ensuite, ajoutez ce qui suit à 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);
}
Cela produit une migration initiale parfaite (où migrationBuilder.CreateTable
est utilisé), et génère le SQL attendu :
`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),
Cela devrait fonctionne également sur les migrations qui mettent à jour les tables existantes, mais assurez-vous que defaultValue
est toujours nul.
Le SetBeforeSaveBehavior
et SetAfterSaveBehavior
les lignes empêchent EF d'essayer de remplacer l'heure de création par une valeur par défaut. Cela rend les colonnes Créé et Mis à jour en lecture seule du point de vue d'EF, permettant à la base de données de faire tout le travail.
Vous pouvez même l'extraire dans une interface et une méthode d'extension :
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;
}
Implémentez ensuite l'interface sur toutes vos entités horodatées :
public class SomeEntity : ITimestampedEntity
{
// Other properties here ...
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}
Cela vous permet de configurer l'entité à partir de OnModelCreating()
comme ça :
protected override void OnModelCreating(ModelBuilder builder)
{
// Other model creating stuff here ...
builder.Entity<SomeTimestampedEntity>().UseTimestampedProperty();
}