如何在使用EF代码的.SaveChanges()期间记录所有实体更改?

时间:2013-07-28 03:11:34

标签: c# logging ef-code-first repository dbcontext

我首先使用 EF代码。我正在为我的所有存储库使用基本存储库,并为存储库注入IUnitofWork

public interface IUnitOfWork : IDisposable
{
    IDbSet<TEntity> Set<TEntity>() where TEntity : class;
    int SaveChanges();
}

public class BaseRepository<T> where T : class
{
    protected readonly DbContext _dbContext;
    protected readonly IDbSet<T> _dbSet;


    public BaseRepository(IUnitOfWork uow)
    {
        _dbContext = (DbContext)uow;
        _dbSet = uow.Set<T>();
    }
    //other methods
}   

例如我的OrderRepository是这样的:

class OrderRepository: BaseRepository<Order>
{
    IUnitOfWork _uow;
    IDbSet<Order> _order;

    public OrderRepository(IUnitOfWork uow)
        : base(uow)
    {
        _uow = uow;
        _order = _uow.Set<Order>();
    }
    //other methods
}

我用这种方式使用它:

public void Save(Order order)
{
        using (IUnitOfWork uow = new MyDBContext())
        {
            OrderRepository repository = new OrderRepository(uow); 
            try
            {
               repository.ApplyChanges<Order>(order);    
               uow.SaveChanges();
            }  

        } 
}     

有没有办法在.SaveChanges()期间记录所有实体的更改历史记录(包括其导航属性)?我想记录原始值(保存发生之前)和更改值(保存发生后)。

4 个答案:

答案 0 :(得分:48)

您可以通过DbContext.ChangeTracker获取所有已更改实体的前后值。不幸的是,API有点冗长:

var changeInfo = context.ChangeTracker.Entries()
            .Where (t => t.State == EntityState.Modified)
            .Select (t => new {
                Original = t.OriginalValues.PropertyNames.ToDictionary (pn => pn, pn => t.OriginalValues[pn]),
                Current = t.CurrentValues.PropertyNames.ToDictionary (pn => pn, pn => t.CurrentValues[pn]),
            });

如果您需要记录,可以修改它以包含实体类型等内容。如果你已经有办法记录整个对象,你可以调用ToObject()(OriginalValues和CurrentValues的类型)上的DbPropertyValues方法,尽管从该方法返回的对象不会有已填充导航属性。

如果根据您的要求更有意义,您也可以通过取出Where子句来修改该代码以获取上下文中的所有实体。

答案 1 :(得分:7)

你因为额外的要求而吓跑了人们

  

包含导航属性

这只是一项非常重要的练习。 如果这很重要,您应该使用代码管理/跟踪引用的更改。

这是一个涵盖此主题的示例 Undo changes in entity framework entities

有一个样本在这里做你想要的顶部 undo changes 它可以很容易地在其他地方的图像之前和之后转换为加载。

在调用DetectChanges之后给定ObjectState条目,您可以实现一个简单的实体实体选项。并按照UOW。但是导航/参考版本使得这个要求非常复杂。

编辑:如何访问changeList

     public class  Repository<TPoco>{
     /....
     public DbEntityEntry<T> Entry(T entity) { return Context.Entry(entity); }

     public virtual IList<ChangePair> GetChanges(object poco) {

        var changes = new List<ObjectPair>();
        var thePoco = (TPoco) poco;

        foreach (var propName in Entry(thePoco).CurrentValues.PropertyNames) {
            var curr = Entry(thePoco).CurrentValues[propName];
            var orig = Entry(thePoco).OriginalValues[propName];
            if (curr != null && orig != null) {
                if (curr.Equals(orig)) {
                    continue;
                }
            }
            if (curr == null && orig == null) {
                continue;
            }
            var aChangePair = new ChangePair {Key = propName, Current = curr, Original = orig};
            changes.Add(aChangePair);
        }
        return changes;
    }
    ///...  partial repository shown
    } 
// FYI the simple return structure

public class ChangePair {
    public string Key { get; set; }
    public object Original { get; set; }
    public object Current { get; set; }
 }

答案 2 :(得分:6)

我已覆盖默认的SaveChanges方法,以记录实体中添加/更新/删除的更改。虽然它不包括导航属性的变化   基于这篇文章:Using entity framework for auditing

public int SaveChanges(string userId)
    {
        int objectsCount;

        List<DbEntityEntry> newEntities = new List<DbEntityEntry>();

        // Get all Added/Deleted/Modified entities (not Unmodified or Detached)
        foreach (var entry in this.ChangeTracker.Entries().Where
            (x => (x.State == System.Data.EntityState.Added) ||
                (x.State == System.Data.EntityState.Deleted) ||
                (x.State == System.Data.EntityState.Modified)))
        {
            if (entry.State == System.Data.EntityState.Added)
            {
                newEntities.Add(entry);
            }
            else
            {
                // For each changed record, get the audit record entries and add them
                foreach (AuditLog changeDescription in GetAuditRecordsForEntity(entry, userId))
                {
                    this.AuditLogs.Add(changeDescription);
                }
            }
        }

        // Default save changes call to actually save changes to the database
        objectsCount = base.SaveChanges();

        // We don't have recordId for insert statements that's why we need to call this method again.
        foreach (var entry in newEntities)
        {
            // For each changed record, get the audit record entries and add them
            foreach (AuditLog changeDescription in GetAuditRecordsForEntity(entry, userId, true))
            {
                this.AuditLogs.Add(changeDescription);
            }

            // TODO: Think about performance here. We are calling db twice for one insertion.
            objectsCount += base.SaveChanges();
        }

        return objectsCount;
    }

    #endregion

    #region Helper Methods

    /// <summary>
    /// Helper method to create record description for Audit table based on operation done on dbEntity
    /// - Insert, Delete, Update
    /// </summary>
    /// <param name="dbEntity"></param>
    /// <param name="userId"></param>
    /// <returns></returns>
    private List<AuditLog> GetAuditRecordsForEntity(DbEntityEntry dbEntity, string userId, bool insertSpecial = false)
    {
        List<AuditLog> changesCollection = new List<AuditLog>();

        DateTime changeTime = DateTime.Now;

        // Get Entity Type Name.
        string tableName1 = dbEntity.GetTableName();

        // http://stackoverflow.com/questions/2281972/how-to-get-a-list-of-properties-with-a-given-attribute
        // Get primary key value (If we have more than one key column, this will need to be adjusted)
        string primaryKeyName = dbEntity.GetAuditRecordKeyName();

        int primaryKeyId = 0;
        object primaryKeyValue;

        if (dbEntity.State == System.Data.EntityState.Added || insertSpecial)
        {
            primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName, true);

            if(primaryKeyValue != null)
            {
                Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
            }                

            // For Inserts, just add the whole record
            // If the dbEntity implements IDescribableEntity,
            // use the description from Describe(), otherwise use ToString()
            changesCollection.Add(new AuditLog()
                    {
                        UserId = userId,
                        EventDate = changeTime,
                        EventType = ModelConstants.UPDATE_TYPE_ADD,
                        TableName = tableName1,
                        RecordId = primaryKeyId,  // Again, adjust this if you have a multi-column key
                        ColumnName = "ALL",    // To show all column names have been changed
                        NewValue = (dbEntity.CurrentValues.ToObject() is IAuditableEntity) ?
                                        (dbEntity.CurrentValues.ToObject() as IAuditableEntity).Describe() :
                                        dbEntity.CurrentValues.ToObject().ToString()
                    }
                );
        }

        else if (dbEntity.State == System.Data.EntityState.Deleted)
        {
            primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName);

            if (primaryKeyValue != null)
            {
                Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
            }

            // With deletes use whole record and get description from Describe() or ToString()
            changesCollection.Add(new AuditLog()
                    {
                        UserId = userId,
                        EventDate = changeTime,
                        EventType = ModelConstants.UPDATE_TYPE_DELETE,
                        TableName = tableName1,
                        RecordId = primaryKeyId,
                        ColumnName = "ALL",
                        OriginalValue = (dbEntity.OriginalValues.ToObject() is IAuditableEntity) ?
                                    (dbEntity.OriginalValues.ToObject() as IAuditableEntity).Describe() :
                                    dbEntity.OriginalValues.ToObject().ToString()
                    });
        }

        else if (dbEntity.State == System.Data.EntityState.Modified)
        {
            primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName);

            if (primaryKeyValue != null)
            {
                Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
            }

            foreach (string propertyName in dbEntity.OriginalValues.PropertyNames)
            {
                // For updates, we only want to capture the columns that actually changed
                if (!object.Equals(dbEntity.OriginalValues.GetValue<object>(propertyName),
                        dbEntity.CurrentValues.GetValue<object>(propertyName)))
                {
                    changesCollection.Add(new AuditLog()
                    {
                        UserId = userId,
                        EventDate = changeTime,
                        EventType = ModelConstants.UPDATE_TYPE_MODIFY,
                        TableName = tableName1,
                        RecordId = primaryKeyId,
                        ColumnName = propertyName,
                        OriginalValue = dbEntity.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntity.OriginalValues.GetValue<object>(propertyName).ToString(),
                        NewValue = dbEntity.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntity.CurrentValues.GetValue<object>(propertyName).ToString()
                    }
                        );
                }
            }
        }


        // Otherwise, don't do anything, we don't care about Unchanged or Detached entities
        return changesCollection;
    }

答案 3 :(得分:3)

DbContext具有 ChangeTracker 属性。 您可以在上下文中覆盖 .SaveChanges()并记录更改。 我不认为实体框架可以为您做到这一点。您可能必须直接在模型类中检测更改。