简体   繁体   English

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

[英]How can I log all entities change, during .SaveChanges() using EF code first?

I'm using EF code first . 使用EF代码 I'm using a base Repository for all my repositories and an IUnitofWork that inject to the repositories, too: 我正在为我的所有存储库使用基本存储库,也为注入存储库的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
}   

eg my OrderRepository is like this: 例如,我的OrderRepository是这样的:

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

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

And I use it in this way: 我用这种方式使用它:

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

        } 
}     

Is there any way to log change histories of all entities(include their navigation properties) during .SaveChanges() ? 有没有办法在.SaveChanges()期间记录所有实体的更改历史记录(包括其导航属性.SaveChanges() I want to log original values (before save occurs) and changed values (after save occurs). 我想记录原始值 (保存发生之前)和更改的值 (保存发生后)。

You can get the before and after values for all changed entities by going through DbContext.ChangeTracker . 您可以通过DbContext.ChangeTracker获取所有已更改实体的前后值。 Unfortunately the API is a little verbose: 不幸的是,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]),
            });

You can modify that to include things like the type of the entity if you need that for your logging. 您可以修改它以包含诸如实体类型之类的内容(如果您需要用于日志记录)。 There is also a ToObject() method on the DbPropertyValues (the type of OriginalValues and CurrentValues) you could call if you already have a way to log whole objects, although the objects returned from that method will not have their navigation properties populated. 如果您已经有办法记录整个对象,则可以调用DbPropertyValues (OriginalValues和CurrentValues的类型ToObject()上的ToObject()方法,尽管从该方法返回的对象不会填充其导航属性。

You can also modify that code to get all entities in the context by taking out the Where clause, if that makes more sense given your requirements. 您还可以通过取出Where子句来修改该代码以获取上下文中的所有实体,如果根据您的要求更有意义的话。

you have scared people away with the extra requirement 你有额外的要求让人们害怕

Include their navigation properties 包括他们的导航属性

This is simply a non trivial exercise. 这简直是​​一项非常重要的工作。 And if this is important, you should manage/track changes across references with code. 如果这很重要,您应该使用代码管理/跟踪引用之间的更改。

this is a sample covering this topic Undo changes in entity framework entities 这是一个涵盖此主题的示例撤消实体框架实体中的更改

There is a sample doing close top what you want here undo changes It can easily be converted to load before and after images elsewhere. 有一个样本在你想要的地方做你想要的撤消更改它可以很容易地转换为在其他地方之前和之后的图像加载。

Given the ObjectState entry after DetectChanges is called, you can implement a simple entity by entity option. 给定DetectChanges之后的ObjectState条目,您可以实现一个简单的实体实体选项。 and per UOW. 并按照UOW。 But the navigation / references version makes this very complex as you worded the requirement. 但导航/参考版本使得这一点变得非常复杂,因为您需要这样做。

EDIT : How to access the changeList 编辑:如何访问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; }
 }

I have overridded the default SaveChanges method to log changes for add/update/delete in entity. 我已覆盖默认的SaveChanges方法,以记录实体中添加/更新/删除的更改。 Though it does not cover navigation property changes. 虽然它不包括导航属性的变化。
Based on this article: 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;
    }

DbContext has ChangeTracker property. DbContext具有ChangeTracker属性。 You can override .SaveChanges() in your context and log changes. 您可以在上下文中覆盖.SaveChanges()并记录更改。 I don't think that entity framework can do it for you. 我不认为实体框架可以为您做到这一点。 Probably, you must detect changes directly in your model classes. 您可能必须直接在模型类中检测更改。

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

相关问题 我如何首先使用可重用的查询/表达式和EF代码在父级中获取子实体作为DTO? - How can I fetch child entities as DTO in parent using reusable queries/Expression's with EF code first? 如何以EF6代码优先的方式删除实体之间的关系? - How can I remove relationship between entities in EF6 code-first? 如何首先使用EF代码为混合实体的集合建模 - How to model collection of mixed entities using EF code first 如何在EF5中首先使用模型覆盖SaveChanges - How to Override SaveChanges Using Model First in EF5 如何在EF中更新相关实体(代码优先) - How to update related entities in EF (code first) 如何使 EF 生成的实体上的所有属性都是虚拟的? - How can I make all properties virtual on entities generated by EF? 首次更新相关实体后,EF Core 2会在SaveChanges上引发异常 - EF Core 2 throws exception on SaveChanges, after first update for related entities EF Code First - 通过反射为所有实体调用MapInheritedProperties() - EF Code First - Call MapInheritedProperties() for all entities by reflection EF Code First从DB不为所有表创建实体 - EF Code First From DB not creating Entities for all tables 即使使用SaveChanges()也无法更改EF中的值 - Cannot change values in EF despite using SaveChanges()
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM