2014-10-30 52 views
1

对于审计日志记录的目的,我需要获取所有列的值,包括已修改为数据库中某个表的FK实体和关系实体。数据库基本上是一个用户可以上传资源(文件,在线文档,图片等)的网站,我有一个名为Material的表格,它具有多个many-2-many和one-2-one关系,如Material - Audience,Material - Category,''材料上传器“,”材料权限Material -Tags等。我想记录发生在Material的所有变化。例如,如果有人去除材料的标签,然后我需要登录:在SaveChanges上使用ObjectContext进行实体框架审计

  • [USER12 - 12年12月12日] - Happy标签得到了Crappy材料中去除。

到目前为止,我得到这个:我可以得到所有这些修改ObjectStateEntries,添加,使用删除:

context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified) 

现在,我可以检查这个ObjectStateEntry是关系或者不使用:

if (e.IsRelationship) { 
    HandleRelationshipEntry(e); 
} 
else { 
    HandleEntry(e); 
} 

HandleEntry方法(项不是关系条目),我可以检查的Entry类型,在我的情况下,它是Material,所以我上来G:

// We care about only Material which are modifed 
if (e.State != EntityState.Modified || !(e.Entity is Material)) 
    return; 

有一次,我知道EntryMaterial Entry型的,我可以用得到的所有已更改为Material表中的列:

e.CurrentValues[ARCHIVE_COLUMN].ToString() != e.OriginalValues[ARCHIVE_COLUMN].ToString() 

在这一点上,我可以记录所有非FK更改Material表。但是,如果列是FK某些其他entity,我不能将该值FK值解析为相应的Entity。我只能知道CategoryID已从42更改为76,但我无法解析Category本身的名称。我试着像铸造DBDataRecordCurrentValueRecordEntityKey,但它只是NULL。有没有什么办法可以使用ObjectStateManager解决这些FKsEntities

我的针对参考全码:

私有类SingleMaterialLogger { MaterialAuditData auditData =新MaterialAuditData(); public void HandleEntity(ObjectStateEntry e,ObjectContext context){ HandlePrimaryTypeChanges(e); HandleComplexTypeChanges(e,context); }

 private void HandleComplexTypeChanges(ObjectStateEntry e, ObjectContext c) { 
      // Owner, Category, Contact 
      ChangeValueHelper(e, CONTACT_COLUMN, (k1, k2) => { 
       // get old value 
       User old = c.GetObjectByKey(k1) as User; 
       User current = c.GetObjectByKey(k2) as User; 
      }); 
     } 

     public void HandlePrimaryTypeChanges(ObjectStateEntry e) { 
      // Name, Description, ArchiveDate, Status 
      // Again no reflection is used - So change them if column name changes 
      ChangeValueHelper<string>(e, NAME_COLUMN, (change) => auditData.Name = change); 
      ChangeValueHelper<string>(e, NAME_COLUMN, (change) => auditData.Description = change); 
      // TODO - Fix change value helper 
      if (e.CurrentValues[ARCHIVE_COLUMN].ToString() != e.OriginalValues[ARCHIVE_COLUMN].ToString()) { 
       auditData.ArchiveDate = new Change<DateTime?>(e.OriginalValues[ARCHIVE_COLUMN] as DateTime?, e.CurrentValues[ARCHIVE_COLUMN] as DateTime?); 
      } 
     } 

     private void ChangeValueHelper(ObjectStateEntry e, string columnName, Action<EntityKey, EntityKey> func) { 
      if (e.CurrentValues[columnName].ToString() != e.OriginalValues[columnName].ToString()) { 
       func(e.OriginalValues[columnName] as EntityKey, e.CurrentValues[columnName] as EntityKey); 
      } 
     } 
     private void ChangeValueHelper<T>(ObjectStateEntry e, string columnName, Action<Change<T>> func) where T : class { 
      if(e.CurrentValues[columnName].ToString() != e.OriginalValues[columnName].ToString()) { 
       func(new Change<T>(e.OriginalValues[columnName] as T, e.OriginalValues[columnName] as T)); 
      } 
     } 
    } 



    Dictionary<EntityKey, SingleMaterialLogger> singleMaterialLoggerMap = new Dictionary<EntityKey, SingleMaterialLogger>(); 
    private ObjectContext context; 

    public MaterialAuditLogger(ObjectContext context) { 
     this.context = context; 
    } 

    public void AuditMaterialChanges() { 
     // Grab everything thats being added/deleted/modified 
     foreach(var e in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified)) { 
      if (e.IsRelationship) { 
       HandleRelationshipEntity(e); 
      } 
      else { 
       HandleEntity(e); 
      } 
     } 
    } 

    private void HandleEntity(ObjectStateEntry e) { 
     // We care about only Material which are modifed 
     if (e.State != EntityState.Modified || !(e.Entity is Material)) 
      return; 

     var logger = SingleLogger(e.EntityKey); 
     logger.HandleEntity(e, context); 
    } 

    private void HandleRelationshipEntity(ObjectStateEntry e) { 
     // relations whose entity keys contains 
    } 

    private SingleMaterialLogger SingleLogger(EntityKey key) { 
     if(singleMaterialLoggerMap.ContainsKey(key)) 
      return singleMaterialLoggerMap[key]; 
     SingleMaterialLogger logger = new SingleMaterialLogger(); 
     singleMaterialLoggerMap[key] = logger; 
     return logger; 
    } 

回答

0

我遇到了同样的问题。

这并不难挽任何实体类型与id值:

DbContext.Set(entityType).Find(id) 

然而,这假定您已经确定在第一位置相关的导航属性的实体类型。这需要一些智慧,基本上通过使用反射来看看属性名称和[ForeignKey的()]属性等

某些选项复制EF逻辑:

1)添加智慧摸出FK模型属性来自FK ID的财产。然后在审计日志创建过程中进行FK模型查询,并将.ToString()值存储在审计日志中。

这是假设:

  • 你在你的DataContext /存储库的通用工具查找在飞行的任何模型类型(例如DbContext.Set(的EntityType).Find(ID)。)

  • 你有信心,你的所有FK模型的ToString()实现将可靠地工作,因为下列之一:

    • 他们从来不靠进一步naviga这可能导致运行时错误

    • 您可以相信的进一步导航性能进行了正确的include()重刑属性“主编模型中的查找

    • 您已启用延迟加载(我强烈反对....但是..这将有助于在这里)

  • 您已经通过交易的影响还以为(如果使用的是超出了EF做交易)

2)将FK ID存储在审计日志中。然后,在查看审计日志时,请即时查看FK模型并在屏幕上呈现ToString()。

我们在这个项目中使用了这个选项,它工作正常。

但是您的审计要求可能会更严格。例如,如果某人更改了FK模型上的名称/描述,那么这似乎会修改旧的审计日志。