一般系統會有登陸日志,操作日志,異常日志,已經滿足大部分的需求了。但是有時候,還是需要Audit 審計日志,審計日志,主要針對數據增,改,刪操作數據變化的記錄,主要是對數據變化的一個追蹤過程。其中主要追蹤數據關鍵點如下
1. 新增 具體新增哪些數據,值是什么,新增人誰。
2. 修改 具體修改哪些數據,之前值是什么,修改后值是什么,修改人誰。
3. 刪除 具體刪除哪些數據,之前值是什么,刪除人誰。
有了這個Audit追蹤過程,當那天,用戶操作的數據出現問題,你就可以根據這個Audit將數據恢復到某個狀態,當然這個Audit,也不僅僅數據出問題才有用,在分析數據時,也有用。
這幾年都在做基於ASP.NET MVC & Entity Framework 搭建企業技術框架,很早之前就想自己寫一個Audit機制,想着把數據變化保存為json,由於之前公司很多事情所有也沒有靜下心來想怎么在現有架構上加這塊的事情。這幾天自己也在找工作(深圳找工作,如果大家的公司缺一個架構的崗位,我可以客串一下,13926537904,不勝感激),所有不忙了,就再次想起Audit來。就開始倒騰起來。
ZZZ Project 這家外國公司,有很多關於.NET和數據訪問的項目,有收費的,有開源的,我之前介紹過 Z.ExtensionMethods 一個強大的開源擴展庫 就出自該名下,其他有 如下
1. Bulk-Operations ,這個我相信大家也不陌生,Ado.Net 批量操作數據組件 收費
2. EntityFramework-Extensions ,這個Entity Framework 擴展庫,這個是EntityFramework 批量操作數據擴展組件 收費
3. EntityFramework-Plus 這個是最近才發布的 Entity Framework + 庫,比EntityFramework-Extensions 新增更多的功能。開源免費
4. Dapper-Plus 這個是最近才發布的 Drpper + 庫
5. Eval-SQL.NET 關於SQL的,沒有使用過,暫時不詳細說。
6. Eval-Expression.NET 關於表達式的,沒有使用過,暫時不詳細說。
等等,還一些我就不一一介紹了,大家可以在下面網站,細看自己感興趣的。
zzz projects GitHub: https://github.com/zzzprojects
我要說的Audit,在 EntityFramework-Plus 庫里面,他有提供,等一下我會實作一下給大家看一下,這個庫所提供的功能,如下
- Batch Operations
- LINQ
- Query
- Audit
如果購買了 EntityFramework-Extensions 這個擴展庫,EntityFramework-Extensions 這個庫的功能也會包含在里面。
EntityFramework-Plus GitHub 主頁 : http://entityframework-plus.net/
EntityFramework-Plus GitHub 源代碼: https://github.com/zzzprojects/EntityFramework-Plus
說了一大堆廢話,沒有入正題,不好意思,開始真正實作 EntityFramework-Plus 之 Audit 部分。
一. 創建解決方案,以及新增基礎設施項目(DbContext,Models,Mapping,Demo),這里就不再介紹了,大家參考 Entity Framework 6 開發系列 目錄 實作一下即可,就只貼一些關鍵代碼以及項目截圖(還未引用EntityFramework-Plus)。
1. 解決方案截圖
EntityFrameworkPlus.Demo 項目:控制台程序,應用層 Demo。
EntityFrameworkPlus.DbContext 項目 :DbContext
EntityFrameworkPlus.Mappings 項目:映射模型
EntityFrameworkPlus.Models 項目:模型
2. 關鍵代碼 (還是以訂單為義務)
OrderModel
using System; namespace EntityFrameworkPlus.Models { public class OrderModel { public Guid OrderGuid { get; set; } public string OrderNo { get; set; } public string OrderCreator { get; set; } public DateTime OrderDateTime { get; set; } public string OrderStatus { get; set; } public string Description { get; set; } public string Creator { get; set; } public DateTime CreateDateTime { get; set; } public string LastModifier { get; set; } public DateTime? LastModifiedDateTime { get; set; } } }
OrderMapp
using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.ModelConfiguration; using EntityFrameworkPlus.Models; namespace EntityFrameworkPlus.Mappings { public class OrderMap : EntityTypeConfiguration<OrderModel> { public OrderMap() { this.HasKey(m => m.OrderGuid); this.Property(m => m.OrderGuid) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.Property(m => m.OrderNo) .IsRequired() .HasMaxLength(30); this.Property(m => m.OrderCreator) .IsRequired() .HasMaxLength(20); this.Property(m => m.OrderStatus) .IsRequired() .HasMaxLength(30); this.Property(m => m.Description) .HasMaxLength(1000); this.Property(m => m.Creator) .IsRequired() .HasMaxLength(20); this.Property(m => m.LastModifier) .HasMaxLength(15) .HasMaxLength(20); this.ToTable("Sample_Order"); this.Property(m => m.OrderGuid).HasColumnName("OrderGuid"); this.Property(m => m.OrderNo).HasColumnName("OrderNo"); this.Property(m => m.OrderCreator).HasColumnName("OrderCreator"); this.Property(m => m.OrderDateTime).HasColumnName("OrderDateTime"); this.Property(m => m.OrderStatus).HasColumnName("OrderStatus"); this.Property(m => m.Description).HasColumnName("Description"); this.Property(m => m.Creator).HasColumnName("Creator"); this.Property(m => m.CreateDateTime).HasColumnName("CreateDateTime"); this.Property(m => m.LastModifier).HasColumnName("LastModifier"); this.Property(m => m.LastModifiedDateTime).HasColumnName("LastModifiedDateTime"); } } }
EntityFrameworkPlusDbContext
using System.Data.Entity; using EntityFrameworkPlus.Mappings; using EntityFrameworkPlus.Models; namespace EntityFrameworkPlus.DbContext { public class EntityFrameworkPlusDbContext : System.Data.Entity.DbContext { public EntityFrameworkPlusDbContext() : base("EntityFrameworkPlusConnection") { } public DbSet<OrderModel> Orders { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new OrderMap()); base.OnModelCreating(modelBuilder); } } }
Program
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using EntityFrameworkPlus.DbContext; using EntityFrameworkPlus.Models; namespace EntityFrameworkPlus.Demo { class Program { static void Main(string[] args) { AddOrder(); } public static void AddOrder() { using (var dbContext = new EntityFrameworkPlusDbContext()) { dbContext.Orders.Add(new OrderModel { OrderNo = "ORDER0001", OrderCreator = "david", OrderDateTime = DateTime.Now, OrderStatus = "已出庫", Creator = "david", CreateDateTime = DateTime.Now }); dbContext.SaveChanges(); } } } }
3. 項目與項目引用關系(代碼圖)
二. EntityFramework-Plus 使用
1. 在EntityFrameworkPlus.DbContext,EntityFrameworkPlus.Demo 兩個項目中安裝 EntityFramework-Plus 庫,右鍵項目 選擇“管理NuGet程序包”,聯機中,搜索“Z.EntityFramework.Plus”(搜到很多個EntityFramework.Plus 開頭組件,大家不要被嚇到了,zzz projects 將 Z.EntityFramework.Plus 組件,按照EntityFramework版本分,然后再按照 Z.EntityFramework.Plus 功能來分,比較獨立,所以會有這么多個組件,我們這里用的是Entity Framework 6 ,要用到Audit功能),選擇 “EntityFramework.Plus (EF6) Audit” 進行安裝。
2. EntityFramework.Plus Audit 把數據追蹤記錄,分成兩個表記錄
AuditEntries 表 ,記錄實體 名稱,所屬上一級命名空間,狀態(增,改,刪),狀態名稱,創建人。
AuditEntryProperties 表,具體數據字段信息,字段名稱,關系名稱,記錄舊,新值。
將 EntityFramework.Plus 提供創建兩個表的SQL語句,在自己的數據庫里面執行。我的數據庫“EntityFrameworkSample”,執行完了,總共就有三個“AuditEntries”,“AuditEntryProperties ”,“Sample_Order”。下面是創建 Audit 相關表SQL,以及數據庫結構截圖。
CREATE TABLE [dbo].[AuditEntries] ( [AuditEntryID] [int] NOT NULL IDENTITY, [EntitySetName] [nvarchar](255), [EntityTypeName] [nvarchar](255), [State] [int] NOT NULL, [StateName] [nvarchar](255), [CreatedBy] [nvarchar](255), [CreatedDate] [datetime] NOT NULL, CONSTRAINT [PK_dbo.AuditEntries] PRIMARY KEY ([AuditEntryID]) ) GO
CREATE TABLE [dbo].[AuditEntryProperties] ( [AuditEntryPropertyID] [int] NOT NULL IDENTITY, [AuditEntryID] [int] NOT NULL, [RelationName] [nvarchar](255), [PropertyName] [nvarchar](255), [OldValue] [nvarchar](max), [NewValue] [nvarchar](max), CONSTRAINT [PK_dbo.AuditEntryProperties] PRIMARY KEY ([AuditEntryPropertyID]) ) GO
CREATE INDEX [IX_AuditEntryID] ON [dbo].[AuditEntryProperties]([AuditEntryID]) GO
ALTER TABLE [dbo].[AuditEntryProperties]
ADD CONSTRAINT [FK_dbo.AuditEntryProperties_dbo.AuditEntries_AuditEntryID]
FOREIGN KEY ([AuditEntryID]) REFERENCES [dbo].[AuditEntries] ([AuditEntryID]) ON DELETE CASCADE
GO
3. 在 “EntityFrameworkPlus.DbContext” 項目的“EntityFrameworkPlusDbContext” ,配置Audit 兩個表實體集合。配置完成代碼如下。
using System.Data.Entity; using EntityFrameworkPlus.Mappings; using EntityFrameworkPlus.Models; using Z.EntityFramework.Plus; namespace EntityFrameworkPlus.DbContext { public class EntityFrameworkPlusDbContext : System.Data.Entity.DbContext { public EntityFrameworkPlusDbContext() : base("EntityFrameworkPlusConnection") { } public DbSet<AuditEntry> AuditEntries { get; set; } public DbSet<AuditEntryProperty> AuditEntryProperties { get; set; } public DbSet<OrderModel> Orders { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new OrderMap()); base.OnModelCreating(modelBuilder); } } }
4. 在“EntityFrameworkPlus.Demo” Program 調整代碼,使用EntityFramework.Plus Audit 代碼,以及增加及調整了 對Sample_Order 增,改,刪數據操作。調整代碼如下。
using System; using System.Data.Entity; using System.Linq; using EntityFrameworkPlus.DbContext; using EntityFrameworkPlus.Models; using Z.EntityFramework.Plus; namespace EntityFrameworkPlus.Demo { class Program { static void Main(string[] args) { AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) => { var customAuditEntries = audit.Entries.Select(x => Import(x)); (context as EntityFrameworkPlusDbContext).AuditEntries.AddRange(customAuditEntries); }; AddOrder(); } public static void AddOrder() { using (var dbContext = new EntityFrameworkPlusDbContext()) { var audit = new Audit { CreatedBy = "david" }; dbContext.Orders.Add(new OrderModel { OrderNo = "ORDER0001", OrderCreator = "david", OrderDateTime = DateTime.Now, OrderStatus = "已出庫", Creator = "david", CreateDateTime = DateTime.Now }); dbContext.SaveChanges(audit); } } public static void UpdateOrder() { using (var dbContext = new EntityFrameworkPlusDbContext()) { var audit = new Audit { CreatedBy = "david" }; var orderAsync = dbContext.Orders.FirstAsync(); var order = orderAsync.Result; order.LastModifier = "davidzhou"; order.LastModifiedDateTime = DateTime.Now; order.OrderStatus = "已完成"; dbContext.Entry(order); dbContext.SaveChanges(audit); } } public static void DeleteOrder() { using (var dbContext = new EntityFrameworkPlusDbContext()) { var audit = new Audit { CreatedBy = "david" }; var orderAsync = dbContext.Orders.FirstAsync(); var order = orderAsync.Result; dbContext.Entry(order).State = EntityState.Deleted; dbContext.SaveChanges(audit); } } public static AuditEntry Import(AuditEntry entry) { var customAuditEntry = new AuditEntry { EntitySetName = entry.EntitySetName, EntityTypeName = entry.EntityTypeName, State = entry.State, StateName = entry.StateName, CreatedBy = entry.CreatedBy, CreatedDate = entry.CreatedDate }; customAuditEntry.Properties = entry.Properties.Select(x => Import(x)).ToList(); return customAuditEntry; } public static AuditEntryProperty Import(AuditEntryProperty property) { var customAuditEntry = new AuditEntryProperty { RelationName = property.RelationName, PropertyName = property.PropertyName, OldValue = property.OldValueFormatted, NewValue = property.NewValueFormatted }; return customAuditEntry; } } }
5. 對 Sample_Order 表,做新增 操作,三張表變化如圖,我這邊就不解釋了,大家應該多看得懂。
5. 對 Sample_Order 表,做修改 操作,三張表變化如圖,我這邊就不解釋了,大家應該多看得懂。
5. 對 Sample_Order 表,做刪除 操作,三張表變化如圖,我這邊就不解釋了,大家應該多看得懂。
好了,到此博文,已經結束,這里要說明的,Entity Framework Plus Audit 部分,我只使用到一些,還要其他大家可以自行,GitHub 更加深入的了解。
此篇博文的源代碼: https://github.com/haibozhou1011/EntityFramework-PlusSample