【问题标题】:Entity Framework 4.1 DbContext Override SaveChanges to Audit Property ChangeEntity Framework 4.1 DbContext 覆盖 SaveChanges 以审计属性更改
【发布时间】:2011-05-27 19:27:14
【问题描述】:

我正在尝试对一组类中的属性进行属性更改的受限“审核日志”。我已经成功地找到了如何设置 CreatedOn|ModifiedOn 类型属性,但我没有找到如何“找到”已修改的属性。

例子:

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 中随着这种详细程度而更改的属性?

【问题讨论】:

    标签: entity-framework


    【解决方案1】:

    非常非常粗略的想法:

    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 的多次调用。

    但 SaveChanges 内部的反射内容可能会成为性能噩梦。

    编辑

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

    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 对应的方法(这是避免反射代码的关键)。

    编辑 2

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

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

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

    【讨论】:

    • 这行得通,但似乎所有属性都是根据这个修改的。将进一步调查,但对原始问题很好。我认为这给了我我需要的东西。
    • 这对我不起作用。旧值始终为空。这仅适用于 POCO 和 4.1 及更高版本吗?
    • @Jonx:是的,它是 EF 4.1 (DbContext) 和 POCO。您可以为 ObjectContext) 执行类似的操作,唯一的区别是您不会覆盖 SaveChanges 而是实现事件处理程序(OnSavingChanges 或类似的,我不记得确切)。
    【解决方案2】:

    您可以使用 Slauma 建议的方法,但您可以处理 SavingChanges 事件,而不是覆盖 SaveChanges() 方法,以便更轻松地实现。

    【讨论】:

    • 哦,好点。实际上,我上面的代码 sn-p 来自 SavingChanges 处理程序,我忘记了。但为什么它让事情变得更容易?写日志需要做的不是基本一样吗?
    • 如果您需要添加它,它会更容易,而不是修改您的覆盖,您只需添加一个处理程序。您也不必担心传递原始功能,我认为您的示例中缺少这些功能。这些对象永远不会真正得到保存。如果您只是编写了一个处理程序,您就不必担心这一点。
    • 真的,谢谢,我忘了调用基类的SaveChanges(现在添加)。 (这是因为我从我的实际 SavingChanges 处理程序中复制了 sn-p 的主要部分。)
    • 有关示例,请参见此处...如何:在保存更改时执行业务逻辑msdn.microsoft.com/en-us/library/cc716714.aspx
    • 我正在尝试获取在添加 EntityState 时定义的字段,以及在删除 EntityState 但 GetModifiedProperties 不包含任何内容时使用的字段。当然可以使用反射来获得它,但有没有更好的方法?
    【解决方案3】:

    似乎 Slauma 的回答并未审核对复杂类型属性的内部属性的更改。

    如果这对您来说是个问题,我的回答 here 可能会有所帮助。如果属性是复杂属性并且它已经改变,我将整个复杂属性序列化到审计日志记录中。这不是最有效的解决方案,但也不算太糟糕,并且可以完成工作。

    【讨论】:

    【解决方案4】:

    我真的很喜欢 Slauma 的解决方案。我通常更喜欢跟踪修改后的表并记录主键。这是一个非常简单的方法,您可以使用它调用getEntityKeys(entry)

        public static string getEntityKeys(ObjectStateEntry entry)
        {
            return string.Join(", ", entry.EntityKey.EntityKeyValues
                                    .Select(x => x.Key + "=" + x.Value));
        }
    

    【讨论】:

      【解决方案5】:

      Using Entity Framework 4.1 DbContext Change Tracking for Audit Logging

      使用 DbEntityEntry。添加、删除、修改的详细审核

      【讨论】:

      • 虽然理论上这可以回答这个问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-06
      • 1970-01-01
      • 1970-01-01
      • 2011-08-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多