c# - 如何使用 EF Core 2.1 和 Pomelo 创建 CreatedOn 和 UpdatedOn
问题描述
在 Code First 方法中,如何定义我的实体,以便:
CreatedOn
NOT NULL - 数据库在插入时使用当前时间戳生成值Updated
NULL - 数据库在更新时使用当前时间戳生成值
样本实体:
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; }
}
数据库上下文:
public class MyContext : DbContext
{
public MyContext(DbContextOptions options) : base(options) {}
public DbSet<MyEntity> Entities { get; set; }
}
数据库中的最终结果应该是:
CreatedOn
NOT NULL - 没有额外的 - 默认可以是 CURRENT_TIMESTAMPUpdatedOn
NULL - 更新时额外的 CURRENT_TIMESTAMP - 无默认值或默认值为 NULL
解决方案
问题:
我已将其缩小为(似乎是)Pomelo 中的一个错误。问题在这里:
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_TIMESTAMP
然后,迁移脚本将使用for吐出正确的 SQL TIMESTAMP
。如果您删除该[Column(TypeName = "TIMESTAMP")]
属性,它将使用一datetime(6)
列并吐出DEFAULT CURRENT_TIMESTAMP(6)
。
解决方案:
我想出了一个解决方法,可以正确实现创建时间(仅在插入时由数据库更新)和更新时间(仅在插入和更新时由数据库更新)。
首先,像这样定义您的实体:
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.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);
}
这会产生一个完美的初始迁移(在哪里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
始终为空。
和SetBeforeSaveBehavior
行SetAfterSaveBehavior
防止 EF 尝试使用默认值覆盖 Created 时间。从 EF 的角度来看,它有效地使 Created 和 Updated 列只读,从而允许数据库完成所有工作。
您甚至可以将其提取到接口和扩展方法中:
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;
}
然后在所有带时间戳的实体上实现接口:
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();
}
推荐阅读
- python - 我正在尝试在 tkinter 中编写一个简单的转换器,但我似乎对 convert 函数有问题
- node.js - 使用 ava 和 proxyquire 用 sinon 加载模块后无法存根方法
- discord.js - 是否可以使用 Discord.js 收集用户过去发送的消息?
- java - Java 内存泄漏与数据源
- ocr - Google Vision API OCR - 不能很好地识别阿拉伯语
- deep-learning - 在 Mac OS El Capitan 下的 Nvidia GeForce GT 120 GPU 上运行 PyTorch 我需要什么版本的 CUDA
- docker - 错误,“无法使用前端 dockerfile.v0 解决”
- flutter - 如何在没有firebase的情况下在flutter中进行深度链接?
- ansible - 将 JWT 令牌插入 Ansible Playbook 的问题
- c - 尝试反转字符串时发生分段错误