如何使用EF Core 2.1和Pomelo创建CreatedOn和UpdatedOn

时间:2018-06-12 18:43:20

标签: c# mysql ef-code-first ef-migrations ef-core-2.0

在Code First方法中,如何定义我的实体以便:

  • CreatedOn NOT NULL - 数据库在插入时使用当前时间戳生成值
  • Updated NULL - db使用当前时间戳
  • 在更新时生成值

示例实体:

public class MyEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column(TypeName = "TIMESTAMP")]
    public DateTime CreatedOn { get; set; }
    [Column(TypeName = "TIMESTAMP")]
    public DateTime UpdatedOn { get; set; }
}

的DbContext:

public class MyContext : DbContext
{
    public MyContext(DbContextOptions options) : base(options) {}

    public DbSet<MyEntity> Entities { get; set; }
}

数据库中的最终结果应为:

  • CreatedOn NOT NULL - 没有额外 - 默认可以是CURRENT_TIMESTAMP
  • UpdatedOn NULL - 更新时额外CURRENT_TIMESTAMP - 无默认值或默认值为NULL

1 个答案:

答案 0 :(得分:0)

问题:

我已将其缩小为(似乎是)柚子中的一个错误。问题在这里:

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

问题是Pomelo在生成迁移时为defaultValue和其他结构创建了DateTime属性。如果在迁移时设置了默认值,它将覆盖值生成策略,然后SQL看起来不正确。

解决方法是生成迁移,然后手动修改迁移文件以将defaultValue设置为null(或删除整行)。

例如,更改此:

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

对此:

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

然后,迁移脚本将使用DEFAULT CURRENT_TIMESTAMPTIMESTAMP吐出正确的SQL。如果删除[Column(TypeName = "TIMESTAMP")]属性,它将使用datetime(6)列并吐出DEFAULT CURRENT_TIMESTAMP(6)

解决方案:

我想出一种解决方法,可以正确实现Created Time(仅在INSERT上由数据库更新)和Updated time(仅在INSERT和UPDATE上由数据库更新)。

首先,像这样定义您的实体:

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

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

然后,将以下内容添加到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.BeforeSaveBehavior = PropertySaveBehavior.Ignore;
    builder.Entity<SomeEntity>.Property(d => d.CreatedTime).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
    builder.Entity<SomeEntity>.Property(d => d.UpdatedTime).Metadata.BeforeSaveBehavior = PropertySaveBehavior.Ignore;
    builder.Entity<SomeEntity>.Property(d => d.UpdatedTime).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
}

这会产生完美的初始迁移(使用migrationBuilder.CreateTable),并生成预期的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),

应该还可用于更新现有表的迁移,但请确保defaultValue始终为空。

PropertySaveBehavior行可防止EF尝试用默认值覆盖Created时间。从EF的角度来看,它有效地使“创建”和“更新”列只读,从而使数据库能够完成所有工作。

您甚至可以将其提取到接口和扩展方法中:

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).Metadata.BeforeSaveBehavior = PropertySaveBehavior.Ignore;
    entity.Property(d => d.CreatedTime).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
    entity.Property(d => d.UpdatedTime).Metadata.BeforeSaveBehavior = PropertySaveBehavior.Ignore;
    entity.Property(d => d.UpdatedTime).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;

    return entity;
}

然后在所有带时间戳的实体上实现该接口:

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

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

这允许您像这样:OnModelCreating()内设置实体

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

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

在固定issue 799之后,此方法也可以与DateTimeOffset一起使用。