Entity Framework 4.1 DbContext 覆盖 SaveChanges 以审计属性更改 [英] Entity Framework 4.1 DbContext Override SaveChanges to Audit Property Change

查看:29
本文介绍了Entity Framework 4.1 DbContext 覆盖 SaveChanges 以审计属性更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试对一组类中的属性实施一个受约束的审计日志"属性更改.我已经成功地找到了如何设置 CreatedOn|ModifiedOn 类型的属性,但我没有找到如何找到"已修改的属性.

I am trying to implement a constrained 'audit log' of property changes to a properties in a set of classes. I have successfully found out how to set CreatedOn|ModifiedOn type properties, but am failing to find out how to 'find' the property that has been modified.

示例:

public class TestContext : DbContext
{
    public override int SaveChanges()
    {
        var utcNowAuditDate = DateTime.UtcNow;
        var changeSet = ChangeTracker.Entries<IAuditable>();
        if (changeSet != null)
            foreach (DbEntityEntry<IAuditable> dbEntityEntry in changeSet)
            {

                switch (dbEntityEntry.State)
                {
                    case EntityState.Added:
                        dbEntityEntry.Entity.CreatedOn = utcNowAuditDate;
                        dbEntityEntry.Entity.ModifiedOn = utcNowAuditDate;
                        break;
                    case EntityState.Modified:
                        dbEntityEntry.Entity.ModifiedOn = utcNowAuditDate;
                        //some way to access the name and value of property that changed here
                        var changedThing = SomeMethodHere(dbEntityEntry);
                        Log.WriteAudit("Entry: {0} Origianl :{1} New: {2}", changedThing.Name,
                                        changedThing.OrigianlValue, changedThing.NewValue)
                        break;
                }
            }
        return base.SaveChanges();
    }
}

那么,有没有办法访问在 EF 4.1 DbContext 中随着这种详细程度而改变的属性?

So, is there a way to access the property that changed with this level of detail in EF 4.1 DbContext?

推荐答案

非常非常粗略的想法:

foreach (var property in dbEntityEntry.Entity.GetType().GetProperties())
{
    DbPropertyEntry propertyEntry = dbEntityEntry.Property(property.Name);
    if (propertyEntry.IsModified)
    {
        Log.WriteAudit("Entry: {0} Original :{1} New: {2}", property.Name,
            propertyEntry.OriginalValue, propertyEntry.CurrentValue);
    }
}

我不知道这是否真的有效,但这是我会尝试的第一步.当然,可能有不止一个属性发生了变化,因此循环和 WriteAudit 可能多次调用.

I have no clue if this would really work in detail, but this is something I would try as a first step. Of course there could be more then one property which has changed, therefore the loop and perhaps multiple calls of WriteAudit.

SaveChanges 中的反射内容可能会成为性能噩梦.

The reflection stuff inside of SaveChanges could become a performance nightmare though.

编辑

也许访问底层 ObjectContext 更好.那么这样的事情是可能的:

Perhaps it is better to access the underlying ObjectContext. Then something like this is possible:

public class TestContext : DbContext
{
    public override int SaveChanges()
    {
        ChangeTracker.DetectChanges(); // Important!

        ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext;

        List<ObjectStateEntry> objectStateEntryList =
            ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Added
                                                       | EntityState.Modified 
                                                       | EntityState.Deleted)
            .ToList();

       foreach (ObjectStateEntry entry in objectStateEntryList)
       {
           if (!entry.IsRelationship)
           {
               switch (entry.State)
               {
                   case EntityState.Added:
                       // write log...
                       break;
                   case EntityState.Deleted:
                       // write log...
                       break;
                   case EntityState.Modified:
                   {
                       foreach (string propertyName in
                                    entry.GetModifiedProperties())
                       {
                           DbDataRecord original = entry.OriginalValues;
                           string oldValue = original.GetValue(
                               original.GetOrdinal(propertyName))
                               .ToString();

                           CurrentValueRecord current = entry.CurrentValues;
                           string newValue = current.GetValue(
                               current.GetOrdinal(propertyName))
                               .ToString();

                           if (oldValue != newValue) // probably not necessary
                           {
                               Log.WriteAudit(
                                   "Entry: {0} Original :{1} New: {2}",
                                   entry.Entity.GetType().Name,
                                   oldValue, newValue);
                           }
                       }
                       break;
                   }
               }
           }
       }
       return base.SaveChanges();
    }
}

我自己在 EF 4.0 中使用过这个.我在DbContext API 中找不到GetModifiedProperties 对应的方法(这是避免反射代码的关键).

I've used this myself in EF 4.0. I cannot find a corresponding method to GetModifiedProperties (which is the key to avoid the reflection code) in the DbContext API.

编辑 2

重要:使用 POCO 实体时,上面的代码需要在开头调用 DbContext.ChangeTracker.DetectChanges().原因是 base.SaveChanges 在这里调用太晚了(在方法的末尾).base.SaveChanges 在内部调用了 DetectChanges,但是因为我们之前要分析和记录更改,所以必须手动调用 DetectChanges 以便 EF 可以找到所有修改过的属性并在更改跟踪器中正确设置状态.

Important: When working with POCO entities the code above needs to call DbContext.ChangeTracker.DetectChanges() at the beginning. The reason is that base.SaveChanges is called too late here (at the end of the method). base.SaveChanges calls DetectChanges internally, but because we want to analyze and log the changes before, we must call DetectChanges manually so that EF can find all modified properties and set the states in the change tracker correctly.

在某些情况下,代码可以在不调用 DetectChanges 的情况下工作,例如,如果使用了 AddRemove 等 DbContext/DbSet 方法在进行最后一次属性修改之后,因为这些方法也在内部调用 DetectChanges.但是如果例如一个实体刚从 DB 加载,一些属性被改变,然后这个派生的 SaveChanges 被调用,自动变化检测不会在 base.SaveChanges 之前发生,最终导致丢失修改属性的日志条目.

There are possible situations where the code can work without calling DetectChanges, for example if DbContext/DbSet methods like Add or Remove are used after the last property modifications are made since these methods also call DetectChanges internally. But if for instance an entity is just loaded from DB, a few properties are changed and then this derived SaveChanges is called, automatic change detection would not happen before base.SaveChanges, finally resulting in missing log entries for modified properties.

我已经相应地更新了上面的代码.

I've updated the code above accordingly.

这篇关于Entity Framework 4.1 DbContext 覆盖 SaveChanges 以审计属性更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆