.Net Core EntityFrameworkCore EFCoreHooks

【EFCoreHooks】讓 EntityFrameworkCore 在寫入資料庫前自動更新系統資訊與刪除資訊

盧家祥 2019/12/30 00:50:09
201

  在開發系統時,有時候我們會希望記錄資料創建者、創建日期時間、更新者與更新日期時間,在某些時候會希望資料不要被刪除或是避免某些時候,使用者勿刪資料時,無法搶救回來,這個時候為了可以統一處理這些問題。

  在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的相關實作。

 

 

盧家祥