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

 知无不言之歌 发布于 2023-02-06 10:33

我正在使用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
    {
        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);


        }
    }

VAAA.. 24

我建议你使用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 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/

2 个回答
  • 我建议你使用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/

    2023-02-06 10:35 回答
  • 我找到了这个NuGet包(TrackerEnabledDbContext)并遵循以下4个步骤:

      安装包TrackerEnabledDbContext

      从TrackerEnabledDbContext命名空间中的TrackerContext继承我的DbContext

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

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

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

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

      databaseContext.SaveChanges(userId);
      

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

    var AuditLogs = db.GetLogs<Proyecto>(id).ToList();
    

    2023-02-06 10:35 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有