我在我所有的实体上都有四个自定义字段。四个字段是CreatedBy
,CreatedDate
,UpdatedBy
和UpdatedDate
。实体框架核心创建和更新字段
有没有办法挂钩实体框架核心事件,以便在插入时它将使用当前用户填充当前DateTime
和CreatedBy
的CreatedDate
?当数据库有更新时,它将与当前用户填充UpdatedDate
当前DateTime
和UpdatedBy
?
我在我所有的实体上都有四个自定义字段。四个字段是CreatedBy
,CreatedDate
,UpdatedBy
和UpdatedDate
。实体框架核心创建和更新字段
有没有办法挂钩实体框架核心事件,以便在插入时它将使用当前用户填充当前DateTime
和CreatedBy
的CreatedDate
?当数据库有更新时,它将与当前用户填充UpdatedDate
当前DateTime
和UpdatedBy
?
基本上@史蒂夫的方法是要走的路,但目前的实施使得它很难单元测试你的项目。
有了一点重构,你可以使它单元测试友好,并保持真实的原则和封装。
这里是史蒂夫的例子的重构版本
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.
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核心的依赖,更好地封装域逻辑方式更好。
当然,人们可以进一步重构这个使用战略模式更多的模块化方法,但这是超出范围。您还可以使用ASP.NET核心样板,其还提供了一个可审核(软删除)EF核心的DbContext的实现(here和here)
我和我所说的“审计”字段的布局完全相同。
我解决这个问题的方法是创建一个名为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;
}
现在,每当我打电话SaveChanges
上我的DbContext(直接或通过存储库),任何继承AuditableEntity
的实体将根据需要设置它的审计字段。
您也可以打包此功能为** **接口'IAuditableEntity'所以你不必强迫你的实体类全部继承基类 –
@marc_s我确实有一个接口(实际上我有更多的关卡),但为了简洁起见我省略了它,因为'AuditableEntity'包含相关的逻辑设置字段。 – Steve
这不是最佳解决方案。 'PrepareSave'方法违反了至少2个最佳实践:1)来自SOLID的SRP:单一责任原则,因为AuditableEntity具有多个责任:追踪字段以更新并保持它自己的状态以及违反分离的担忧。2)它也违反了封装:在所有实体中声明'EntityState'作为公共方法的一个参数,现在你已经将所有的**层紧密结合在一起了,例如域/业务层和持久性技术 – Tseng
我喜欢这个想法。一个问题可能是如果你有一个不可审计的实体或者另一种保存设置的实体,那么这种方法将无法工作,除非你保留了多个不同类型的dbcontext。 – Steve
@Steve:'ChangeTracker.Entries().TypeOf()'已经过滤可审计的实体。但是如果想要进一步模块化,可以使用策略模式并将其分割为多个“IEntitySaveHandler”或“IEntitySaveHandler”,然后必须使用方法:CanHandle和Handle方法。然后每个处理程序可以检查它是否可以应用到实体,如果是,则执行更改。这种方法也会遵循开放/关闭的原则(开放扩展,关闭修改),你可以添加新的处理程序而不修改基本的DbContext –
Tseng
但是它有点超出一个问题的范围 – Tseng