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

时间:2014-01-06 23:51:20

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

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


        }
    }

8 个答案:

答案 0 :(得分:24)

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

答案 1 :(得分:8)

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

  1. 安装包TrackerEnabledDbContext

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

    public class ApplicationDbContext :  TrackerContext 
        {
            public ApplicationDbContext()
                : base("DefaultConnection")
            {
            }
    
  3. 添加迁移并更新我的数据库。为记录更改创建了两个新表(AuditLog和AuditLogDetails)。

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

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

      databaseContext.SaveChanges(userId);
      
    3. 这就是全部。稍后您可以使用以下方法检索日志:

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

答案 2 :(得分: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

答案 3 :(得分:2)

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

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

  1. 我不想写一个sql server trigger
  2. 我不想在每个实体中处理savechanges方法。
  3. 所以我打算编写通用的单一方法

    我正在使用的Db结构

    审核表

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

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

    3.为Dbcontext savechanges

    挂钩通用事件处理程序

    c#c​​ode

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

  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;

        }

答案 6 :(得分:0)

您可以这样做,而无需任何用于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();
}

enter image description here

型号:

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

来源:

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类型的更多信息,并从那里创建适合您需求的解决方案。

如果您需要整个Entity Framework快照历史记录,请查看this answer

答案 7 :(得分:-1)

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