简体   繁体   English

如何使用实体框架 5 和 MVC 4 创建审计跟踪

[英]how to create an audit trail with Entity framework 5 and MVC 4

I am building an MVC 4 application, using EF 5. I need to do an audit trail, ie log any changes that end users make.我正在构建一个 MVC 4 应用程序,使用 EF 5。我需要做一个审计跟踪,即记录最终用户所做的任何更改。

I have asked this question a few times, but haven't really gotten a satisfying answer before.这个问题我已经问过几次了,但之前都没有真正得到满意的答案。 So I am adding a lot more details in hoping to get somewhere..所以我添加了更多细节,希望能到达某个地方。

currently I have multiple repositories目前我有多个存储库

ie IE

 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;
            }
        }



    }

Now I know for me to add the code to check to see if there are any changes i need to add it in the try of the save.现在我知道我添加代码来检查是否有任何更改我需要在保存的尝试中添加它。 Before the context.SaveChanges().在 context.SaveChanges() 之前。

But currently I have 10 repos.但目前我有 10 个回购。 I don't really want to add code to 10 different places.我真的不想将代码添加到 10 个不同的地方。 As this code will do exactly the same thing.因为这段代码将做完全相同的事情。 I want to somehow have a baseclass that the repos inherit from.我想以某种方式拥有一个 repos 继承的基类。

any help?有什么帮助吗? any sample code?任何示例代码? any pointers?任何指针?

would be appreciated.将不胜感激。 I am sure other people would have done this before我相信其他人以前会这样做

I am mappying my keys, relationships and tables like so我正在像这样映射我的键、关系和表

 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);


        }
    }

What I recommend you is to use the ChangeTracker property in EF.我建议您使用 EF 中的 ChangeTracker 属性。

Inside your DBContext.cs you will have this:在您的 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;
        }


    }

This will use the following table in your DB:这将在您的数据库中使用下表:

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

With this all set then you will just need to call your dbContext.SaveChanges(here the userId);设置好这些之后,您只需要调用 dbContext.SaveChanges(此处为 userId);

Hope this will work for you... I use it in all my applications and works great!希望这对你有用......我在我所有的应用程序中都使用它并且效果很好!

Enjoy it.享受吧。


Full code found here: https://jmdority.wordpress.com/2011/07/20/using-entity-framework-4-1-dbcontext-change-tracking-for-audit-logging/完整代码在这里找到: https : //jmdority.wordpress.com/2011/07/20/using-entity-framework-4-1-dbcontext-change-tracking-for-audit-logging/

I found this NuGet package ( TrackerEnabledDbContext ) and followed these 4 steps:我找到了这个 NuGet 包( TrackerEnabledDbContext )并遵循以下 4 个步骤:

  1. Install package TrackerEnabledDbContext安装包 TrackerEnabledDbContext

  2. Inherit my DbContext from TrackerContext in TrackerEnabledDbContext namespace从 TrackerEnabledDbContext 命名空间中的 TrackerContext 继承我的 DbContext

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

Add a migration and update my database.添加迁移并更新我的数据库。 Two new tables were created for recording changes (AuditLog and AuditLogDetails).创建了两个新表来记录更改(AuditLog 和 AuditLogDetails)。

  1. Decide which tables you want to track and apply [TrackChanges] attribute to the classes.决定要跟踪哪些表并将[TrackChanges]属性应用于类。 In case you want to skip tracking for some specific columns, you can apply [SkipTracking] attribute to those columns (properties).如果您想跳过某些特定列的跟踪,您可以将[SkipTracking]属性应用于这些列(属性)。

  2. Whenever you make a change in database, you call DbContext.SaveChanges() .每当您在数据库中进行更改时,您都会调用DbContext.SaveChanges() Now you have an overload available for that which takes an integer.现在您有一个可用于接受整数的重载。 This should be the logged-in person's user ID.这应该是登录人的用户 ID。 If you don't pass the user ID, this change will not be recorded into the tracking table.如果不传递用户 ID,则此更改将不会记录到跟踪表中。

     databaseContext.SaveChanges(userId);

And that's all.仅此而已。 Later you can retrieve the logs with this:稍后您可以使用以下方法检索日志:

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

Disclaimer : I'm the owner of the project Entity Framework Plus免责声明:我是Entity Framework Plus项目的所有者

EF+ has auditing features which support EF5, EF6, and EF Core. 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)
    {
    }
}

A lot of options are available like an AutoSave in the database.许多选项都可用,例如数据库中的自动保存。

Documentation: EF+ Audit文档: EF+ 审计

In Generic repository pattern, we can write a generic event handler for db context savechanges event.在通用存储库模式中,我们可以为 db context savechanges 事件编写一个通用的事件处理程序。

I googled it and gathered few many information.我用谷歌搜索并收集了很多信息。

  1. I dont want to write a sql server trigger我不想写一个sql server触发器
  2. I dont want to handle savechanges method in each entity.我不想在每个实体中处理 savechanges 方法。

So i am planning to write generic single method所以我打算编写通用的单一方法

Db structure that I am using我正在使用的数据库结构

audit table审计表

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.Update your dbcontext with Audit table entity. 2. 使用审计表实体更新您的 dbcontext。

3.Hook generic event handler for Dbcontext savechanges 3.Hook 通用事件处理程序用于 Dbcontext savechanges

c# code代码

    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);
                    }
                } 
            }   
        }
    }

Create a class for capture the changes or track the changes when entity added, modifies or deleted.创建一个类,用于在实体添加、修改或删除时捕获更改或跟踪更改。

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

Then create audit table entity and context class.然后创建审计表实体和上下文类。

And Override savechanges method in this method get audit changes and save before base entity saved.并在此方法中覆盖 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;
        }
    }
}

You can do it like this without any external library for Entity Framework Core:您可以这样做,而无需 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();
}

在此处输入图片说明

Model:型号:

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; }
}

DbContext:数据库上下文:

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;
    }
}

Source:来源:

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

If you are using Entity Framework 6 or Entity Framework Core you could also use Audit.NET and Audit.EntityFramework written by @thepirat000.如果您使用的是 Entity Framework 6 或 Entity Framework Core,您还可以使用 @thepirat000 编写的Audit.NETAudit.EntityFramework This works fine but I like to have a minimum of NuGet dependencies, preferably 0, that are not backed by a large community/corporation and that depends heavily on a single developer.这工作正常,但我喜欢最少的 NuGet 依赖项,最好是 0,这些依赖项不受大型社区/公司的支持,并且严重依赖于单个开发人员。

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

You can also read more about Slowly changing dimension types and from there create a solution that fits your needs.您还可以阅读有关缓慢变化维度类型的更多信息,并从中创建适合您需求的解决方案。

If you need entire Entity Framework Snapshot History look at this answer .如果您需要整个 Entity Framework Snapshot History,请查看此答案

  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;

        }

Another option is to create an audit action attribute to allow methods to be decorated with an attribute describing the audit operation.另一种选择是创建审计操作属性,以允许使用描述审计操作的属性来修饰方法。 Just inherit from Attribute and list the information you want to capture in the constructor, then create an interceptor (inherit from castle) to intercept request to a method.只需要继承Attribute,在构造函数中列出你要捕获的信息,然后创建一个拦截器(从 Castle 继承)来拦截对方法的请求。 The interceptor will invoke the audit service (which is just a simple class that will write the audit message to the database).拦截器将调用审计服务(它只是一个将审计消息写入数据库的简单类)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM