我的所有实体都有四个自定义字段。这四个字段为CreatedBy
,CreatedDate
,UpdatedBy
和UpdatedDate
。
是否有办法挂钩实体框架核心事件,以便在插入时,它将使用当前用户填充当前CreatedDate
和DateTime
的{{1}}?当数据库有更新时,它会使用当前用户填充CreatedBy
当前UpdatedDate
和DateTime
吗?
答案 0 :(得分:9)
基本上,@ Steve的方法是可行的,但目前的实施方式使得很难对项目进行单元测试。
通过一些重构,您可以使其单元测试友好,并坚持SOLID原则和封装。
这是史蒂夫的例子
的重构版本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 Core依赖性,更好地封装了你的域逻辑方式。
当然,可以通过使用策略模式进一步重构这一点以使用更模块化的方法,但这超出了范围。您还可以使用ASP.NET Core Boilerplate,它还提供可审计(和软删除)EF Core DbContext(here和here)的实现
答案 1 :(得分:2)
我的布局与我所说的完全相同"审核"字段。
我解决这个问题的方法是创建一个名为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
设为虚拟,因此如果需要,我可以在我的实体中覆盖它。您可能需要根据实施情况更改身份获取方式。
为了打电话给我,我在我的SaveChanges
上覆盖DbContext
,并在每个添加或更新的实体(我从更改跟踪器获得)上调用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
的实体都会根据需要设置审核字段。
答案 2 :(得分:0)
很遗憾,我的声誉因添加评论而很低。因此,我添加了我的答案。所以: 曾答一。当实体处于修改状态时,必须将IsModidied = false设置为CreatedBy和CreatedDate属性。否则,将使用字符串和DateTime类型的可选值重写该字段。
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;
}