如何在使用EF代码的.SaveChanges()期间记录所有实体更改?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在使用EF代码的.SaveChanges()期间记录所有实体更改?相关的知识,希望对你有一定的参考价值。
我先使用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()
期间记录所有实体的更改历史记录(包括其导航属性)?我想记录原始值(保存发生之前)和更改的值(保存发生后)。
您可以通过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
子句来修改该代码以获取上下文中的所有实体,如果根据您的要求更有意义的话。
你有额外的要求让人们害怕
包括他们的导航属性
这简直是一项非常重要的工作。如果这很重要,您应该使用代码管理/跟踪引用之间的更改。
这是一个涵盖此主题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; }
}
我已覆盖默认的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() :
以上是关于如何在使用EF代码的.SaveChanges()期间记录所有实体更改?的主要内容,如果未能解决你的问题,请参考以下文章
如果在 Db 上修改了先前加载的实体,如何将 EF 设置为不让 saveChanges?
何时在 EF 6 中使用 BeginTransaction? vs SaveChanges [重复]
在 EF 中调用 SaveChanges() 后将获取的旧记录保留在变量中