C# 在 .SaveChanges() 期间,如何首先使用 EF 代码记录所有实体更改?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17904631/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 10:46:41  来源:igfitidea点击:

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

c#loggingef-code-firstrepositorydbcontext

提问by Masoud

I'm using EF code first. I'm using a base Repository for all my repositories and an IUnitofWorkthat inject to the repositories, too:

首先使用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
}   

e.g my OrderRepositoryis 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()? I want to log original values(before save occurs) and changed values(after save occurs).

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

采纳答案by Steve Ruble

You can get the before and after values for all changed entities by going through DbContext.ChangeTracker. Unfortunately the API is a little verbose:

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

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.

如果您的日志记录需要,您可以修改它以包含诸如实体类型之类的内容。还有一ToObject()对方法DbPropertyValues(OriginalValues和CurrentValues的类型),你可以打电话,如果你已经有了一个地记录整个对象,虽然从该方法返回的对象不会有填充他们的导航属性。

You can also modify that code to get all entities in the context by taking out the Whereclause, if that makes more sense given your requirements.

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

回答by phil soady

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 changesIt 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. and per UOW. But the navigation / references version makes this very complex as you worded the requirement.

给定调用 DetectChanges 后的 ObjectState 条目,您可以通过实体选项实现一个简单的实体。和每个 UOW。但是当您提出要求时,导航/参考版本使这变得非常复杂。

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

回答by Roman Bats

DbContext has ChangeTrackerproperty. You can override .SaveChanges()in your context and log changes. I don't think that entity framework can do it for you. Probably, you must detect changes directly in your model classes.

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

回答by Rohit

I have overridded the default SaveChanges method to log changes for add/update/delete in entity. Though it does not cover navigation property changes.
Based on this article: Using entity framework for auditing

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

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