【问题标题】:how to create an audit trail with Entity framework 5 and MVC 4如何使用实体框架 5 和 MVC 4 创建审计跟踪
【发布时间】:2014-01-24 12:53:22
【问题描述】:

我正在使用 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 个不同的地方。因为这段代码会做同样的事情。我想以某种方式拥有一个 repos 继承自的基类。

有什么帮助吗?任何示例代码?任何指针?

将不胜感激。我相信其他人之前会这样做

我正在像这样映射我的键、关系和表格

 public class AuditZoneMap : EntityTypeConfiguration<AuditZone>
    {
        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);


        }
    }

【问题讨论】:

标签: c# .net asp.net-mvc-4 entity-framework-5


【解决方案1】:

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

在您的 DBContext.cs 中,您将拥有以下内容:

public class DBContext : DbContext
    {

        public DBContext () : base("DatabaseName")
        {

        }



        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {


        }

        public DbSet<YourPocoModelNameHere > 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<AuditLog> GetAuditRecordsForChange(DbEntityEntry dbEntry, int userId)
        {
            List<AuditLog> result = new List<AuditLog>();

            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<object>(keyName).ToString(),
                        ColumnName = propertyName,
                        NewValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntry.CurrentValues.GetValue<object>(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<object>(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<object>(propertyName), dbEntry.CurrentValues.GetValue<object>(propertyName)))
                    {
                        result.Add(new AuditLog()
                        {
                            AuditLogId = Guid.NewGuid(),
                            UserId = userId,
                            EventDateUTC = changeTime,
                            EventType = "M",    // Modified
                            TableName = tableName,
                            RecordId = dbEntry.OriginalValues.GetValue<object>(keyName).ToString(),
                            ColumnName = propertyName,
                            OriginalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntry.OriginalValues.GetValue<object>(propertyName).ToString(),
                            NewValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntry.CurrentValues.GetValue<object>(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 p. m. ******/
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/

【讨论】:

  • VAAA - 谢谢你......这正是我一直在寻找的东西......我会尽快尝试并让你知道......
  • VAAA - 我尝试了您的建议,但遇到了一些问题。我正在使用地图来配置主键、关系和表名。我没有明确地将 [Key] 数据注释放在 poco 模型上。请参阅上面的更新代码,所以当我使用您的代码时,它不知道密钥是什么,要修复它,我可以简单地更新我的 poco 模型,但是我在复制我的工作,你知道我可以如何使用我现有的基础设施吗?
  • 嗨,很奇怪,但我在使用 EntityTypeConfiguration 进行映射时遇到了同样的问题。您是否使用 EntityTypeConfiguration 让它工作?谢谢
  • 另外,recordid只有在保存后才知道,所以对于审计.Added状态,id总是0,有什么解决办法吗?添加条目后刷新条目,然后更新审计日志似乎有点过头了。
【解决方案2】:

我找到了这个 NuGet 包 (TrackerEnabledDbContext) 并按照以下 4 个步骤操作:

  1. 安装包TrackerEnabledDbContext

  2. 从 TrackerEnabledDbContext 命名空间中的 TrackerContext 继承我的 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<Proyecto>(id).ToList();

【讨论】:

  • 如何在 SaveChanges() 方法中获取会话 userId 或 userName 参数。或者应该应用哪种方法才能将用户名或 id 参数作为 userId 传递给 SaveChanges 方法?另一方面,如果为用户名和用户 ID 创建一个新表更好,他们应该在哪一步检索,即在控制器阶段或 SaveChanges() 方法中的 DbContext 阶段?提前致谢。
  • 我可以覆盖 SaveChanges() ,获取用户 ID,然后调用 SaveChanges(user_id),它认为它比更改我的控制器上的所有 SaceChanges() 调用更好,以便在我的控制器上检索名称我创建了一个助手类 History,获取此通用实体的所有日志,过滤或排序日志,然后使用存储的 Id 从我的用户表中获取用户名。 var aLogs = _db.GetLogs(id).OrderByDescending(x => x.EventDateUTC).ToList(); foreach (var log in aLogs) { log.UserName = _db.Users.Find(iduser).DisplayName;
  • 在我的情况下,我使用 Windows 身份验证(Intranet 应用程序中的 Active Directory),您可以调用 db.SaveChanges(db.Users.First(x=>x.AdName == User.Identity.Name )。ID); AdName 是存储在我的数据库中的用户(“域\用户”)的活动目录名称,您可以将其与当前登录用户的 User.Identity.Name 进行比较,然后您将获得 Id...
  • 谢谢。但是在使用该引用后遇到另一个错误:“Xxxx.Domain.Entities.User'不包含'Identity'的定义......”
  • 我的错误,如果您尝试在控制器外部使用身份,您可以使用:HttpContext.Current.User.Identity.Name 获取当前用户名,然后在您的用户表中使用此名称搜索按名称并获取 Id,另一种方式是请求控制器内的当前用户名。让我知道这是否适合你。
【解决方案3】:

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

EF+ 具有支持 EF5、EF6 和 EF Core 的审核功能。

// 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

【讨论】:

    【解决方案4】:

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

    我用谷歌搜索并收集了一些信息。

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

    所以我打算写一个通用的单一方法

    我正在使用的数据库结构

    审核表

    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.使用审核表实体更新您的 dbcontext。

    3.Hook 通用事件处理程序用于 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<Audit> auditTrailList = new Collection<Audit>();
    
                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<ObjectStateEntry> 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);
                        }
                    } 
                }   
            }
        }
    

    【讨论】:

    • 这是一个答案,还是你自己的问题?
    【解决方案5】:

    创建一个类以在实体添加、修改或删除时捕获更改或跟踪更改。

    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<long> TableIdValue { get; set; }
            public Nullable<System.DateTime> UpdateDate { get; set; }
        }
    
    
        public class StdContext : DbContext
        {
            private AuditTrailFactory auditFactory;
            private List<Audit> auditList = new List<Audit>();
            private List<DbEntityEntry> objectList = new List<DbEntityEntry>();
            public StdContext() : base("stdConnection")
            {
                Database.SetInitializer<StdContext>(new CreateDatabaseIfNotExists<StdContext>());
            }
    
            public DbSet<Student> Student { get; set; }
            public DbSet<Audit> 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;
            }
        }
    }
    

    【讨论】:

      【解决方案6】:

      您可以在没有任何 Entity Framework Core 外部库的情况下这样做:

      using (var context = new SampleContext())
      {
          // Insert a row
          var customer = new Customer();
          customer.FirstName = "John";
          customer.LastName = "doe";
          context.Customers.Add(customer);
          await context.SaveChangesAsync();
      
          // Update the first customer
          customer.LastName = "Doe";
          await context.SaveChangesAsync();
      
          // Delete the customer
          context.Customers.Remove(customer);
          await context.SaveChangesAsync();
      }
      

      型号:

      public class Audit
      {
          public int Id { get; set; }
          public string TableName { get; set; }
          public DateTime DateTime { get; set; }
          public string KeyValues { get; set; }
          public string OldValues { get; set; }
          public string NewValues { get; set; }
      }
      
      public class Customer
      {
          public int Id { get; set; }
          public string FirstName { get; set; }
          public string LastName { get; set; }
      }
      
      public class SampleContext : DbContext
      {
          public DbSet<Customer> Customers { get; set; }
          public DbSet<Audit> Audits { get; set; }
      }
      

      数据库上下文:

      public class SampleContext : DbContext
      {
          public DbSet<Customer> Customers { get; set; }
          public DbSet<Audit> Audits { get; set; }
      
          public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
          {
              var auditEntries = OnBeforeSaveChanges();
              var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
              await OnAfterSaveChanges(auditEntries);
              return result;
          }
      
          private List<AuditEntry> OnBeforeSaveChanges()
          {
              ChangeTracker.DetectChanges();
              var auditEntries = new List<AuditEntry>();
              foreach (var entry in ChangeTracker.Entries())
              {
                  if (entry.Entity is Audit || entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
                      continue;
      
                  var auditEntry = new AuditEntry(entry);
                  auditEntry.TableName = entry.Metadata.Relational().TableName;
                  auditEntries.Add(auditEntry);
      
                  foreach (var property in entry.Properties)
                  {
                      if (property.IsTemporary)
                      {
                          // value will be generated by the database, get the value after saving
                          auditEntry.TemporaryProperties.Add(property);
                          continue;
                      }
      
                      string propertyName = property.Metadata.Name;
                      if (property.Metadata.IsPrimaryKey())
                      {
                          auditEntry.KeyValues[propertyName] = property.CurrentValue;
                          continue;
                      }
      
                      switch (entry.State)
                      {
                          case EntityState.Added:
                              auditEntry.NewValues[propertyName] = property.CurrentValue;
                              break;
      
                          case EntityState.Deleted:
                              auditEntry.OldValues[propertyName] = property.OriginalValue;
                              break;
      
                          case EntityState.Modified:
                              if (property.IsModified)
                              {
                                  auditEntry.OldValues[propertyName] = property.OriginalValue;
                                  auditEntry.NewValues[propertyName] = property.CurrentValue;
                              }
                              break;
                      }
                  }
              }
      
              // Save audit entities that have all the modifications
              foreach (var auditEntry in auditEntries.Where(_ => !_.HasTemporaryProperties))
              {
                  Audits.Add(auditEntry.ToAudit());
              }
      
              // keep a list of entries where the value of some properties are unknown at this step
              return auditEntries.Where(_ => _.HasTemporaryProperties).ToList();
          }
      
          private Task OnAfterSaveChanges(List<AuditEntry> auditEntries)
          {
              if (auditEntries == null || auditEntries.Count == 0)
                  return Task.CompletedTask
      
              foreach (var auditEntry in auditEntries)
              {
                  // Get the final value of the temporary properties
                  foreach (var prop in auditEntry.TemporaryProperties)
                  {
                      if (prop.Metadata.IsPrimaryKey())
                      {
                          auditEntry.KeyValues[prop.Metadata.Name] = prop.CurrentValue;
                      }
                      else
                      {
                          auditEntry.NewValues[prop.Metadata.Name] = prop.CurrentValue;
                      }
                  }
      
                  // Save the Audit entry
                  Audits.Add(auditEntry.ToAudit());
              }
      
              return SaveChangesAsync();
          }
      }
      
      public class AuditEntry
      {
          public AuditEntry(EntityEntry entry)
          {
              Entry = entry;
          }
      
          public EntityEntry Entry { get; }
          public string TableName { get; set; }
          public Dictionary<string, object> KeyValues { get; } = new Dictionary<string, object>();
          public Dictionary<string, object> OldValues { get; } = new Dictionary<string, object>();
          public Dictionary<string, object> NewValues { get; } = new Dictionary<string, object>();
          public List<PropertyEntry> TemporaryProperties { get; } = new List<PropertyEntry>();
      
          public bool HasTemporaryProperties => TemporaryProperties.Any();
      
          public Audit ToAudit()
          {
              var audit = new Audit();
              audit.TableName = TableName;
              audit.DateTime = DateTime.UtcNow;
              audit.KeyValues = JsonConvert.SerializeObject(KeyValues);
              audit.OldValues = OldValues.Count == 0 ? null : JsonConvert.SerializeObject(OldValues);
              audit.NewValues = NewValues.Count == 0 ? null : JsonConvert.SerializeObject(NewValues);
              return audit;
          }
      }
      

      来源:

      https://www.meziantou.net/entity-framework-core-history-audit-table.htm

      如果您使用的是 Entity Framework 6 或 Entity Framework Core,您还可以使用@thepirat000 编写的Audit.NETAudit.EntityFramework。这很好用,但我希望拥有最少的 NuGet 依赖项,最好是 0,这些依赖项不受大型社区/公司的支持,并且严重依赖于单个开发人员。

      https://github.com/thepirat000/Audit.NET/graphs/contributors

      您还可以阅读有关Slowly changing dimension 类型的更多信息,并从中创建适合您需求的解决方案。

      如果您需要完整的实体框架快照历史记录,请查看 this answer

      【讨论】:

        【解决方案7】:
          public override int SaveChanges()
                {
                    var auditEntries = new List<AuditEntry>();
        
                    var modifiedEntities = ChangeTracker.Entries()
                        .Where(p => p.State == EntityState.Modified).ToList();
        
                    foreach (var change in modifiedEntities)
                    {
                        var auditEntry = new AuditEntry(change);
                        var primaryKey = GetPrimaryKeys(change);
                        auditEntry.TableName = change.Entity.GetType().Name;//get table name
                       // var id = change.CurrentValues.GetValue<object>("Id").ToString();//get current id
                        auditEntry.EntityId = primaryKey.ToString();
                        auditEntry.EntityTypeId = primaryKey.ToString();
                        auditEntries.Add(auditEntry);
        
        
                        foreach (var prop in change.OriginalValues.PropertyNames)
                        {
                            if (prop == "Id")
                            {
                                continue;
                            }
        
                            switch (change.State)
                            {
                                case EntityState.Modified:
                                    if ((change.State & EntityState.Modified) != 0)
                                    {
                                        auditEntry.OldValues[prop] = change.OriginalValues[prop].ToString();
                                        auditEntry.NewValues[prop] = change.CurrentValues[prop].ToString();
                                    }
                                    break;
                            }
        
                        }
                        foreach (var auditEntry1 in auditEntries.Where(_ => !_.HasTemporaryProperties))
                        {
                            Audits.Add(auditEntry1.ToAudit());
                        }
                    }
                    return base.SaveChanges();
                }
        
                private object GetPrimaryKeys(DbEntityEntry entry)
                {
                    var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
        
                    return objectStateEntry.EntityKey.EntityKeyValues[0].Value;
        
                }
        

        【讨论】:

        • 使用实体框架 mvc 5 在上面应用审计日志
        【解决方案8】:

        另一个选项是创建一个审计操作属性,以允许使用描述审计操作的属性来修饰方法。只需继承自Attribute,并在构造函数中列出要捕获的信息,然后创建一个拦截器(从castle继承)来拦截对方法的请求。拦截器将调用审计服务(它只是一个简单的类,将审计消息写入数据库)。

        【讨论】:

        • 这永远不会“记录最终用户所做的任何更改”。
        猜你喜欢
        • 2018-05-15
        • 2014-12-08
        • 2016-09-15
        • 1970-01-01
        • 2019-12-02
        • 2011-08-24
        • 1970-01-01
        • 2011-11-01
        • 2011-08-25
        相关资源
        最近更新 更多