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

使用EF代码 。 此外,我正在为我的所有存储库和注入存储库的IUnitofWork使用基本存储库:

 public interface IUnitOfWork : IDisposable { IDbSet Set() where TEntity : class; int SaveChanges(); } public class BaseRepository where T : class { protected readonly DbContext _dbContext; protected readonly IDbSet _dbSet; public BaseRepository(IUnitOfWork uow) { _dbContext = (DbContext)uow; _dbSet = uow.Set(); } //other methods } 

例如,我的OrderRepository是这样的:

 class OrderRepository: BaseRepository { IUnitOfWork _uow; IDbSet _order; public OrderRepository(IUnitOfWork uow) : base(uow) { _uow = uow; _order = _uow.Set(); } //other methods } 

我用这种方式使用它:

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

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

您可以通过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]), }); 

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

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

你有额外的要求让人们害怕

包括他们的导航属性

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

这是一个涵盖此主题的示例撤消entity framework实体中的更改

有一个样本做得很接近您想要的内容撤消更改它可以很容易地在其他地方的图像之前和之后转换为加载。

给定DetectChanges之后的ObjectState条目,您可以实现一个简单的实体实体选项。 并按照UOW。 但导航/参考版本使得这一点非常复杂,因为您需要说明这一点。

编辑:如何访问changeList

  public class Repository{ /.... public DbEntityEntry Entry(T entity) { return Context.Entry(entity); } public virtual IList GetChanges(object poco) { var changes = new List(); 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; } } 

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

 public int SaveChanges(string userId) { int objectsCount; List newEntities = new List(); // 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 ///  /// Helper method to create record description for Audit table based on operation done on dbEntity /// - Insert, Delete, Update ///  ///  ///  ///  private List GetAuditRecordsForEntity(DbEntityEntry dbEntity, string userId, bool insertSpecial = false) { List changesCollection = new List(); 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(propertyName), dbEntity.CurrentValues.GetValue(propertyName))) { changesCollection.Add(new AuditLog() { UserId = userId, EventDate = changeTime, EventType = ModelConstants.UPDATE_TYPE_MODIFY, TableName = tableName1, RecordId = primaryKeyId, ColumnName = propertyName, OriginalValue = dbEntity.OriginalValues.GetValue(propertyName) == null ? null : dbEntity.OriginalValues.GetValue(propertyName).ToString(), NewValue = dbEntity.CurrentValues.GetValue(propertyName) == null ? null : dbEntity.CurrentValues.GetValue(propertyName).ToString() } ); } } } // Otherwise, don't do anything, we don't care about Unchanged or Detached entities return changesCollection; } 

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