[英]Entity Framework Core Creation and Update fields
我的所有实体上都有四个自定义字段。 这四个字段是CreatedBy
、 CreatedDate
、 UpdatedBy
和UpdatedDate
。
有没有办法挂钩到 Entity Framework 核心事件,以便在插入时它将使用当前的DateTime
填充CreatedDate
并使用当前用户填充CreatedBy
? 当数据库有更新时,它会用当前的DateTime
填充UpdatedDate
,用当前用户填充UpdatedBy
?
基本上@Steve 的方法是可行的方法,但它的当前实现使得对您的项目进行单元测试变得困难。
通过一点点重构,您可以使其单元测试友好,并忠于 SOLID 原则和封装。
这是 Steve 示例的重构版本
public abstract class AuditableEntity
{
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
public DateTime UpdatedDate { get; set; }
public string UpdatedBy { get; set; }
}
public class AuditableDbContext : DbContext
{
protected readonly IUserService userService;
protected readonly DbContextOptions options;
protected readonly ITimeService timeService;
public BaseDbContext(DbContextOptions options, IUserService userService, ITimeService timeService) : base(options)
{
userService = userService ?? throw new ArgumentNullException(nameof(userService));
timeService = timeService ?? throw new ArgumentNullException(nameof(timeService));
}
public override int SaveChanges()
{
// get entries that are being Added or Updated
var modifiedEntries = ChangeTracker.Entries()
.Where(x => (x.State == EntityState.Added || x.State == EntityState.Modified));
var identityName = userService.CurrentUser.Name;
var now = timeService.CurrentTime;
foreach (var entry in modifiedEntries)
{
var entity = entry.Entity as AuditableEntity;
if (entry.State == EntityState.Added)
{
entity.CreatedBy = identityName ?? "unknown";
entity.CreatedDate = now;
}
entity.UpdatedBy = identityName ?? "unknown";
entity.UpdatedDate = now;
}
return base.SaveChanges();
}
}
现在很容易模拟单元测试的时间和用户/主体,并且模型/域/业务层没有 EF Core 依赖,更好地封装您的域逻辑。
当然,可以通过使用策略模式进一步重构它以使用更模块化的方法,但这超出了范围。 您还可以使用 ASP.NET Core Boilerplate,它也提供了可审计(和软删除)EF Core DbContext(这里和这里)的实现
我和你的布局完全一样,我称之为“审计”字段。
我解决这个问题的方法是创建一个名为AuditableEntity
的基本抽象类来保存属性本身并公开一个名为PrepareSave
的方法。 在PrepareSave
我根据需要设置字段的值:
public abstract class AuditableEntity
{
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
public DateTime UpdatedDate { get; set; }
public string UpdatedBy { get; set; }
public virtual void PrepareSave(EntityState state)
{
var identityName = Thread.CurrentPrincipal.Identity.Name;
var now = DateTime.UtcNow;
if (state == EntityState.Added)
{
CreatedBy = identityName ?? "unknown";
CreatedDate = now;
}
UpdatedBy = identityName ?? "unknown";
UpdatedDate = now;
}
}
我将PrepareSave
虚拟,因此如果需要,我可以在我的实体中覆盖它。 您可能需要根据您的实施更改获取身份的方式。
为了调用它,我在我的DbContext
上覆盖了SaveChanges
并在每个正在添加或更新的实体上调用PrepareSave
(我从更改跟踪器中获得):
public override int SaveChanges()
{
// get entries that are being Added or Updated
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);
foreach (var entry in modifiedEntries)
{
// try and convert to an Auditable Entity
var entity = entry.Entity as AuditableEntity;
// call PrepareSave on the entity, telling it the state it is in
entity?.PrepareSave(entry.State);
}
var result = base.SaveChanges();
return result;
}
现在,每当我在 DbContext 上调用SaveChanges
(直接或通过存储库)时,任何继承AuditableEntity
实体都会根据需要设置其审计字段。
不幸的是,我在添加评论方面的声誉很低。 出于这个原因,我添加了我的答案。 所以: 曾回答的一个补充。 当实体处于修改状态时,您必须设置 IsModidied = false 或 CreatedBy 和 CreatedDate 属性。 否则,这些字段将被字符串和日期时间类型的可选值重写。
if (entry.State == EntityState.added)
{
CreatedBy = identityName ?? "unknown";
CreatedDate = now;
}
else
{
Entry<AuditableEntity>(entry).Property(p => p.CreatedBy).IsModified = false;
Entry<AuditableEntity>(entry).Property(p => p.CreatedDate).IsModified = false;
// or
// entity.Property("CreatedBy").IsModified = false;
// entity.Property("CreatedDate").IsModified = false;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.