【EFCoreHooks】讓 EntityFrameworkCore 在寫入資料庫前自動更新系統資訊與刪除資訊
在開發系統時,有時候我們會希望記錄資料創建者、創建日期時間、更新者與更新日期時間,在某些時候會希望資料不要被刪除或是避免某些時候,使用者勿刪資料時,無法搶救回來,這個時候為了可以統一處理這些問題。
在Entity Framework 開發時,會使用 Atreyu.EFHooks,讓我們可以在執行 SaveChanges 前,依照我們指定的儲存器狀態,分別執行對應的Insert、Update或Delete,所設定好的Hook,來進行想要執行的資料額外處理,因此相當適用於需要Entity都必須要遵循的規則,例如更新或新增系統所需的系統資訊欄位,或是刪除改成更新 IsValid 來表示資料的刪除,並記錄刪除者與刪除時間。
接下來在NuGet上尋找有沒有類似EFHooks,因此我決定自己撰寫一個功能跟EFHooks差不多的EFCoreHook,這部分就不多作介紹,直接附上GitHub,想了解的可以上我的GitHub。
GitHub:EFCoreHooks
接下來是來定義我們希望系統自動更新或新增的系統資訊欄位
/// <summary>
/// 系統資訊介面
/// </summary>
public interface ISystemInfo
{
/// <summary>
/// 創立者
/// </summary>
string CreatedBy { get; set; }
/// <summary>
/// 創立時間
/// </summary>
DateTime CreatedAt { get; set; }
/// <summary>
/// 更新者
/// </summary>
string UpdatedBy { get; set; }
/// <summary>
/// 更新時間
/// </summary>
DateTime UpdatedAt { get; set; }
}
建立SystemInfoInsertHook 和 SystemInfoUpdateHook 分別繼承了 InsertHook<T> 與 UpdateHook<T> ,這樣我們就能在觸發Hook時更新或新增系統資訊。
public class SystemInfoInsertHook: InsertHook<ISystemInfo>
{
public SystemInfoInsertHook(IHttpContextAccessor httpContextAccessor)
{
HttpContext = httpContextAccessor.HttpContext;
}
public HttpContext HttpContext { get; }
public override Task Hook(ISystemInfo entity, DbContext dbContext)
{
var userAccount = "SystemInfoIo";
if (HttpContext != null && HttpContext.User.Identity.IsAuthenticated)
{
userAccount = HttpContext.User.Identity.Name;
}
entity.CreatedBy = userAccount;
entity.CreatedAt = DateTime.UtcNow;
entity.UpdatedBy = userAccount;
entity.UpdatedAt = DateTime.UtcNow;
return Task.CompletedTask;
}
}
public class SystemInfoUpdateHook : UpdateHook<ISystemInfo>
{
public SystemInfoUpdateHook(IHttpContextAccessor httpContextAccessor)
{
HttpContext = httpContextAccessor.HttpContext;
}
public HttpContext HttpContext { get; }
public override Task Hook(ISystemInfo entity, DbContext dbContext)
{
var userAccount = "Sys";
if (HttpContext != null && HttpContext.User.Identity.IsAuthenticated)
{
userAccount = HttpContext.User.Identity.Name;
}
entity.UpdatedBy = userAccount;
entity.UpdatedAt = DateTime.UtcNow;
return Task.CompletedTask;
}
}
再來是刪除的部分,將刪除改成更新 IsValid 旗標欄位,並記錄刪除者與刪除日期時間。
建立 DeleteInfoDeleteHook 繼承 DeleteHook<T>。
public class DeleteInfoDeleteHook : DeleteHook<IDeleteInfo>
{
public DeleteInfoDeleteHook(IHttpContextAccessor httpContextAccessor)
{
HttpContext = httpContextAccessor.HttpContext;
}
public HttpContext HttpContext { get; }
public override Task Hook(IDeleteInfo entity, DbContext dbContext)
{
var userAccount = "Sys";
if (HttpContext != null && HttpContext.User.Identity.IsAuthenticated)
{
userAccount = HttpContext.User.Identity.Name;
}
entity.DeleteAt = DateTime.UtcNow;
entity.DeleteBy = userAccount;
entity.IsValid = false;
dbContext.Entry(entity).State = EntityState.Modified;
return Task.CompletedTask;
}
}
這樣我們就能在透過 Hook 在執行刪除時,先將 Model 的 EntityState從刪除改成更新,並且同時對 IsValid 欄位作變更,因此原本Entity Framework Core 的刪除行為就不會被執行了!
接下來建立 BaseEntity 讓專案需要使用這些規則的 Table ,繼承 BaseEntity。
public class BaseEntity : ISystemInfo, IDeleteInfo
{
/// <summary>
/// 創立者
/// </summary>
public string CreatedBy { get; set; } = "Sys";
/// <summary>
/// 創立時間
/// </summary>
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// 更新者
/// </summary>
public string UpdatedBy { get; set; } = "Sys";
/// <summary>
/// 更新時間
/// </summary>
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// 是否有效
/// </summary>
public bool IsValid { get; set; } = true;
/// <summary>
/// 刪除者
/// </summary>
public string DeleteBy { get; set; } = string.Empty;
/// <summary>
/// 刪除時間
/// </summary>
public DateTime? DeleteAt { get; set; }
}
最後在 Startup 註冊相關的 DI ,在這邊除了.Net Core 提供的 DI 註冊,其他的在這邊都使用 Autofac DI 註冊
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
}
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.Load("EFCoreHooks.Hooks"))
.AsImplementedInterfaces();
}
DbContext 的部分需要繼承 HookDbContext,並設定Hook
public TestDbContext(IEnumerable<IHook> hooks, DbContextOptions<TestDbContext> options)
: base(hooks, options)
{
}
以上就是EFCoreHooks的相關實作。