如何使用Entity framework 5和MVC 4创建审计跟踪

我正在使用EF 5构建MVC 4应用程序。我需要执行审计跟踪,即记录最终用户所做的任何更改。

我已经问了几次这个问题,但之前没有真正得到满意的答案。 所以我希望在某个地方添加更多细节……

目前我有多个存储库

public class AuditZoneRepository : IAuditZoneRepository { private AISDbContext context = new AISDbContext(); public int Save(AuditZone model, ModelStateDictionary modelState) { if (model.Id == 0) { context.AuditZones.Add(model); } else { var recordToUpdate = context.AuditZones.FirstOrDefault(x => x.Id == model.Id); if (recordToUpdate != null) { recordToUpdate.Description = model.Description; recordToUpdate.Valid = model.Valid; recordToUpdate.ModifiedDate = DateTime.Now; } } try { context.SaveChanges(); return 1; } catch (Exception ex) { modelState.AddModelError("", "Database error has occured. Please try again later"); return -1; } } } public class PostcodesRepository : IPostcodesRepository { private AISDbContext context = new AISDbContext(); public int Save(Postcodes model, ModelStateDictionary modelState) { if (model.Id == 0) { context.Postcodes.Add(model); } else { var recordToUpdate = context.Postcodes.FirstOrDefault(x => x.Id == model.Id); if (recordToUpdate != null) { recordToUpdate.Suburb = model.Suburb; recordToUpdate.State = model.State; recordToUpdate.Postcode = model.Postcode; recordToUpdate.AuditZoneId = model.AuditZoneId; recordToUpdate.ModifiedDate = DateTime.Now; } } try { context.SaveChanges(); return 1; } catch (Exception ex) { modelState.AddModelError("", "Database error has occured. Please try again later"); return -1; } } } 

现在我知道我要添加代码来检查是否有任何更改我需要在保存尝试中添加它。 在context.SaveChanges()之前。

但目前我有10个回购。 我真的不想将代码添加到10个不同的地方。 因为这段代码会做同样的事情。 我想以某种方式拥有一个reposinheritance的基类。

任何帮助? 任何示例代码? 任何指针?

不胜感激。 我相信其他人之前会做到这一点

我正在映射我的钥匙,关系和表格

  public class AuditZoneMap : EntityTypeConfiguration { public AuditZoneMap() { // Primary Key HasKey(t => t.Id); // Properties Property(t => t.Description) .HasMaxLength(100); // Table & Column Mappings ToTable("AuditZone"); Property(t => t.Id).HasColumnName("Id"); Property(t => t.Description).HasColumnName("Description"); Property(t => t.Valid).HasColumnName("Valid"); Property(t => t.CreatedDate).HasColumnName("CreatedDate"); Property(t => t.CreatedBy).HasColumnName("CreatedBy"); Property(t => t.ModifiedDate).HasColumnName("ModifiedDate"); Property(t => t.ModifiedBy).HasColumnName("ModifiedBy"); // Relationships HasOptional(t => t.CreatedByUser) .WithMany(t => t.CreatedByAuditZone) .HasForeignKey(d => d.CreatedBy); HasOptional(t => t.ModifiedByUser) .WithMany(t => t.ModifiedByAuditZone) .HasForeignKey(d => d.ModifiedBy); } } 

我建议你使用EF中的ChangeTracker属性。

在您的DBContext.cs中,您将拥有:

 public class DBContext : DbContext { public DBContext () : base("DatabaseName") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { } public DbSet YourPocoModelNameHere { get; set; } // This is overridden to prevent someone from calling SaveChanges without specifying the user making the change public override int SaveChanges() { throw new InvalidOperationException("User ID must be provided"); } public int SaveChanges(int userId) { // Get all Added/Deleted/Modified entities (not Unmodified or Detached) foreach (var ent in this.ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Added || p.State == System.Data.EntityState.Deleted || p.State == System.Data.EntityState.Modified)) { // For each changed record, get the audit record entries and add them foreach (AuditLog x in GetAuditRecordsForChange(ent, userId)) { this.AuditLogs.Add(x); } } // Call the original SaveChanges(), which will save both the changes made and the audit records return base.SaveChanges(); } private List GetAuditRecordsForChange(DbEntityEntry dbEntry, int userId) { List result = new List(); DateTime changeTime = DateTime.UtcNow; // Get the Table() attribute, if one exists //TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute; TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), true).SingleOrDefault() as TableAttribute; // Get table name (if it has a Table attribute, use that, otherwise get the pluralized name) string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name; // Get primary key value (If you have more than one key column, this will need to be adjusted) var keyNames = dbEntry.Entity.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Count() > 0).ToList(); string keyName = keyNames[0].Name; //dbEntry.Entity.GetType().GetProperties().Single(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Count() > 0).Name; if (dbEntry.State == System.Data.EntityState.Added) { // For Inserts, just add the whole record // If the entity implements IDescribableEntity, use the description from Describe(), otherwise use ToString() foreach (string propertyName in dbEntry.CurrentValues.PropertyNames) { result.Add(new AuditLog() { AuditLogId = Guid.NewGuid(), UserId = userId, EventDateUTC = changeTime, EventType = "A", // Added TableName = tableName, RecordId = dbEntry.CurrentValues.GetValue(keyName).ToString(), ColumnName = propertyName, NewValue = dbEntry.CurrentValues.GetValue(propertyName) == null ? null : dbEntry.CurrentValues.GetValue(propertyName).ToString() } ); } } else if (dbEntry.State == System.Data.EntityState.Deleted) { // Same with deletes, do the whole record, and use either the description from Describe() or ToString() result.Add(new AuditLog() { AuditLogId = Guid.NewGuid(), UserId = userId, EventDateUTC = changeTime, EventType = "D", // Deleted TableName = tableName, RecordId = dbEntry.OriginalValues.GetValue(keyName).ToString(), ColumnName = "*ALL", NewValue = (dbEntry.OriginalValues.ToObject() is IDescribableEntity) ? (dbEntry.OriginalValues.ToObject() as IDescribableEntity).Describe() : dbEntry.OriginalValues.ToObject().ToString() } ); } else if (dbEntry.State == System.Data.EntityState.Modified) { foreach (string propertyName in dbEntry.OriginalValues.PropertyNames) { // For updates, we only want to capture the columns that actually changed if (!object.Equals(dbEntry.OriginalValues.GetValue(propertyName), dbEntry.CurrentValues.GetValue(propertyName))) { result.Add(new AuditLog() { AuditLogId = Guid.NewGuid(), UserId = userId, EventDateUTC = changeTime, EventType = "M", // Modified TableName = tableName, RecordId = dbEntry.OriginalValues.GetValue(keyName).ToString(), ColumnName = propertyName, OriginalValue = dbEntry.OriginalValues.GetValue(propertyName) == null ? null : dbEntry.OriginalValues.GetValue(propertyName).ToString(), NewValue = dbEntry.CurrentValues.GetValue(propertyName) == null ? null : dbEntry.CurrentValues.GetValue(propertyName).ToString() } ); } } } // Otherwise, don't do anything, we don't care about Unchanged or Detached entities return result; } } 

这将在您的数据库中使用以下表格:

 USE [databasename] GO /****** Object: Table [dbo].[auditlog] Script Date: 06/01/2014 05:56:49 pm ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[auditlog]( [auditlogid] [uniqueidentifier] NOT NULL, [userid] [int] NOT NULL, [eventdateutc] [datetime] NOT NULL, [eventtype] [char](1) NOT NULL, [tablename] [nvarchar](100) NOT NULL, [recordid] [nvarchar](100) NOT NULL, [columnname] [nvarchar](100) NOT NULL, [originalvalue] [nvarchar](max) NULL, [newvalue] [nvarchar](max) NULL, CONSTRAINT [PK_AuditLog] PRIMARY KEY NONCLUSTERED ( [auditlogid] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO SET ANSI_PADDING OFF GO ALTER TABLE [dbo].[auditlog] WITH CHECK ADD CONSTRAINT [FK_auditlog_users] FOREIGN KEY([userid]) REFERENCES [dbo].[users] ([userid]) GO ALTER TABLE [dbo].[auditlog] CHECK CONSTRAINT [FK_auditlog_users] GO 

有了这一切,那么你只需要调用你的dbContext.SaveChanges(这里是userId);

希望这对你有用……我在我的所有应用程序中使用它并且工作得很好!

好好享受。


完整代码可在此处找到: https : //jmdority.wordpress.com/2011/07/20/using-entity-framework-4-1-dbcontext-change-tracking-for-audit-logging/

我找到了这个NuGet包( TrackerEnabledDbContext )并遵循以下4个步骤:

  1. 安装包TrackerEnabledDbContext

  2. 从TrackerEnabledDbContext命名空间中的TrackerContextinheritance我的DbContext

     public class ApplicationDbContext : TrackerContext { public ApplicationDbContext() : base("DefaultConnection") { } 

添加迁移并更新我的数据库。 为记录更改创建了两个新表(AuditLog和AuditLogDetails)。

  1. 确定要跟踪的表,并将[TrackChanges]属性应用于类。 如果您想跳过某些特定列的跟踪,可以将[SkipTracking]属性应用于这些列(属性)。

  2. 每当您在数据库中进行更改时,都会调用DbContext.SaveChanges() 。 现在你有一个可用的重载,它接受一个整数。 这应该是登录人员的用户ID。 如果未传递用户ID,则此更改不会记录到跟踪表中。

     databaseContext.SaveChanges(userId); 

就这样。 稍后您可以使用以下方法检索日志:

 var AuditLogs = db.GetLogs(id).ToList(); 

免责声明 :我是项目Entity Framework Plus的所有者

EF +具有支持EF5,EF6和EF Core的审计function。

 // using Z.EntityFramework.Plus; // Don't forget to include this. var ctx = new EntityContext(); // ... ctx changes ... var audit = new Audit(); audit.CreatedBy = "ZZZ Projects"; // Optional ctx.SaveChanges(audit); // Access to all auditing information var entries = audit.Entries; foreach(var entry in entries) { foreach(var property in entry.Properties) { } } 

有许多选项可供使用,例如数据库中的AutoSave。

文档: EF + Audit

在Generic repository模式中,我们可以为db context savechanges事件编写通用事件处理程序。

我用Google搜索并收集了很多信息。

  1. 我不想写一个sql server触发器
  2. 我不想在每个实体中处理savechanges方法。

所以我打算写一般的单一方法

我正在使用的Db结构

审计表

 CREATE TABLE [dbo].[Audit]( [Id] [BIGINT] IDENTITY(1,1) NOT NULL, [TableName] [nvarchar](250) NULL, [Updated By] [nvarchar](100) NULL, [Actions] [nvarchar](25) NULL, [OldData] [text] NULL, [NewData] [text] NULL, [Created For] varchar(200) NULL, [Updated Date] [datetime] NULL, CONSTRAINT [PK_DBAudit] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 

2.使用Audit表实体更新dbcontext。

3.为Dbcontext savechanges挂钩通用事件处理程序

c#代码

  namespace ARMS.Domain { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Data.Objects; using System.Linq; using System.Text; using System.ComponentModel.DataAnnotations; public partial class ARMSContext { Collection auditTrailList = new Collection(); partial void OnContextCreated() { this.SavingChanges += new EventHandler(ArmsEntities_SavingChanges); } public enum AuditActions { Added, Modified, Deleted } void ArmsEntities_SavingChanges(object sender, EventArgs e) { auditTrailList.Clear(); IEnumerable changes = this.ObjectStateManager.GetObjectStateEntries( EntityState.Added | EntityState.Deleted | EntityState.Modified); foreach (ObjectStateEntry stateEntryEntity in changes) { if (!stateEntryEntity.IsRelationship && stateEntryEntity.Entity != null && !(stateEntryEntity.Entity is Audit)) { Audit audit = this.GetAudit(stateEntryEntity); auditTrailList.Add(audit); } } if (auditTrailList.Count > 0) { foreach (var audit in auditTrailList) { this.Audits.AddObject(audit); } } } public Audit GetAudit(ObjectStateEntry entry) { Audit audit = new Audit(); audit.Updated_By ="Test"; audit.TableName = entry.EntitySet.ToString(); audit.Updated_Date = DateTime.Now; audit.Created_For = Convert.ToString(entry.Entity); audit.Actions = Enum.Parse(typeof(AuditActions),entry.State.ToString(), true).ToString(); StringBuilder newValues = new StringBuilder(); StringBuilder oldValues = new StringBuilder(); if (entry.State == EntityState.Added) { SetAddedProperties(entry, newValues); audit.NewData = newValues.ToString(); } else if (entry.State == EntityState.Deleted) { SetDeletedProperties(entry, oldValues); audit.OldData = oldValues.ToString(); } else if (entry.State == EntityState.Modified) { SetModifiedProperties(entry, oldValues, newValues); audit.OldData = oldValues.ToString(); audit.NewData = newValues.ToString(); } return audit; } private void SetAddedProperties(ObjectStateEntry entry, StringBuilder newData) { CurrentValueRecord currentValues = entry.CurrentValues; for (int i = 0; i < currentValues.FieldCount; i++) { newData.AppendFormat("{0}={1} || ", currentValues.GetName(i), currentValues.GetValue(i)); } } private void SetDeletedProperties(ObjectStateEntry entry, StringBuilder oldData) { foreach (var propertyName in entry.GetModifiedProperties()) { var oldVal = entry.OriginalValues[propertyName]; if (oldVal != null) { oldData.AppendFormat("{0}={1} || ", propertyName, oldVal); } } } private void SetModifiedProperties(ObjectStateEntry entry, StringBuilder oldData, StringBuilder newData) { foreach (var propertyName in entry.GetModifiedProperties()) { var oldVal = entry.OriginalValues[propertyName]; var newVal = entry.CurrentValues[propertyName]; if (oldVal != null && newVal != null && !Equals(oldVal, newVal)) { newData.AppendFormat("{0}={1} || ", propertyName, newVal); oldData.AppendFormat("{0}={1} || ", propertyName, oldVal); } } } } } 

创建一个类来捕获更改或跟踪实体添加,修改或删除时的更改。

 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Text; using System.Web; namespace MVC_AuditTrail.Models { public class AuditTrailFactory { private DbContext context; public AuditTrailFactory(DbContext context) { this.context = context; } public Audit GetAudit(DbEntityEntry entry) { Audit audit = new Audit(); // var user = (User)HttpContext.Current.Session[":user"]; audit.UserId = "swapnil";// user.UserName; audit.TableName = GetTableName(entry); audit.UpdateDate = DateTime.Now; audit.TableIdValue = GetKeyValue(entry); //entry is Added if (entry.State == EntityState.Added) { var newValues = new StringBuilder(); SetAddedProperties(entry, newValues); audit.NewData = newValues.ToString(); audit.Actions = AuditActions.I.ToString(); } //entry in deleted else if (entry.State == EntityState.Deleted) { var oldValues = new StringBuilder(); SetDeletedProperties(entry, oldValues); audit.OldData = oldValues.ToString(); audit.Actions = AuditActions.D.ToString(); } //entry is modified else if (entry.State == EntityState.Modified) { var oldValues = new StringBuilder(); var newValues = new StringBuilder(); SetModifiedProperties(entry, oldValues, newValues); audit.OldData = oldValues.ToString(); audit.NewData = newValues.ToString(); audit.Actions = AuditActions.U.ToString(); } return audit; } private void SetAddedProperties(DbEntityEntry entry, StringBuilder newData) { foreach (var propertyName in entry.CurrentValues.PropertyNames) { var newVal = entry.CurrentValues[propertyName]; if (newVal != null) { newData.AppendFormat("{0}={1} || ", propertyName, newVal); } } if (newData.Length > 0) newData = newData.Remove(newData.Length - 3, 3); } private void SetDeletedProperties(DbEntityEntry entry, StringBuilder oldData) { DbPropertyValues dbValues = entry.GetDatabaseValues(); foreach (var propertyName in dbValues.PropertyNames) { var oldVal = dbValues[propertyName]; if (oldVal != null) { oldData.AppendFormat("{0}={1} || ", propertyName, oldVal); } } if (oldData.Length > 0) oldData = oldData.Remove(oldData.Length - 3, 3); } private void SetModifiedProperties(DbEntityEntry entry, StringBuilder oldData, StringBuilder newData) { DbPropertyValues dbValues = entry.GetDatabaseValues(); foreach (var propertyName in entry.OriginalValues.PropertyNames) { var oldVal = dbValues[propertyName]; var newVal = entry.CurrentValues[propertyName]; if (oldVal != null && newVal != null && !Equals(oldVal, newVal)) { newData.AppendFormat("{0}={1} || ", propertyName, newVal); oldData.AppendFormat("{0}={1} || ", propertyName, oldVal); } } if (oldData.Length > 0) oldData = oldData.Remove(oldData.Length - 3, 3); if (newData.Length > 0) newData = newData.Remove(newData.Length - 3, 3); } public long? GetKeyValue(DbEntityEntry entry) { var objectStateEntry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity); long id = 0; if (objectStateEntry.EntityKey.EntityKeyValues != null) id = Convert.ToInt64(objectStateEntry.EntityKey.EntityKeyValues[0].Value); return id; } private string GetTableName(DbEntityEntry dbEntry) { TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute; string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name; return tableName; } } public enum AuditActions { I, U, D } } 

然后创建审计表实体和上下文类。

并且此方法中的覆盖savechanges方法获取审计更改并在保存基本实体之前保存。

 using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Web; namespace MVC_AuditTrail.Models { public class Student { public int StudentID { get; set; } public string Name { get; set; } public string mobile { get; set; } } public class Audit { public long Id { get; set; } public string TableName { get; set; } public string UserId { get; set; } public string Actions { get; set; } public string OldData { get; set; } public string NewData { get; set; } public Nullable TableIdValue { get; set; } public Nullable UpdateDate { get; set; } } public class StdContext : DbContext { private AuditTrailFactory auditFactory; private List auditList = new List(); private List objectList = new List(); public StdContext() : base("stdConnection") { Database.SetInitializer(new CreateDatabaseIfNotExists()); } public DbSet Student { get; set; } public DbSet Audit { get; set; } public override int SaveChanges() { auditList.Clear(); objectList.Clear(); auditFactory = new AuditTrailFactory(this); var entityList = ChangeTracker.Entries().Where(p => p.State == EntityState.Added || p.State == EntityState.Deleted || p.State == EntityState.Modified); foreach (var entity in entityList) { Audit audit = auditFactory.GetAudit(entity); bool isValid = true; if (entity.State == EntityState.Modified && string.IsNullOrWhiteSpace(audit.NewData) && string.IsNullOrWhiteSpace(audit.OldData)) { isValid = false; } if (isValid) { auditList.Add(audit); objectList.Add(entity); } } var retVal = base.SaveChanges(); if (auditList.Count > 0) { int i = 0; foreach (var audit in auditList) { if (audit.Actions == AuditActions.I.ToString()) audit.TableIdValue = auditFactory.GetKeyValue(objectList[i]); this.Audit.Add(audit); i++; } base.SaveChanges(); } return retVal; } } }