在SaveChanges上使用ObjectContext进行实体框架审核
本文关键字:实体 框架 ObjectContext SaveChanges | 更新日期: 2023-09-27 18:28:27
为了进行审计日志记录,我需要获取所有列的值,包括为数据库中的某个表修改的FK实体和关系实体。数据库基本上是为用户可以上传资源(文件、在线文档、图片等)的网站而建的,我有一个名为Material
的表,它有多个多个二元和一个二元的关系,如Material - Audience
、Material - Category
、"材料上传器"、"材料权限Material -Tags
等。我想记录Material
发生的所有更改。例如,如果有人从材料中删除标签,那么我需要记录:
- [User12-12/12]-
Happy
标签已从Crappy
材料中删除
到目前为止,我得到的是:我可以使用获得所有修改、添加、删除的ObjectStateEntries
context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified)
现在,我可以使用检查这个ObjectStateEntry
是否是RelationShip
if (e.IsRelationship) {
HandleRelationshipEntry(e);
}
else {
HandleEntry(e);
}
在HandleEntry
方法中(条目不是关系条目),我可以检查Entry
的类型,在我的情况下是Material
,所以我正在做:
// We care about only Material which are modifed
if (e.State != EntityState.Modified || !(e.Entity is Material))
return;
一旦我知道Entry
的类型是Material Entry
,我就可以使用获取Material
表中所有更改的列
e.CurrentValues[ARCHIVE_COLUMN].ToString() != e.OriginalValues[ARCHIVE_COLUMN].ToString()
此时,我可以记录Material
表的所有非FK更改。但如果列是FK
到其他entity
,我就无法将该FK
值解析为相应的Entity
。我只知道CategoryID
已经从42
更改为76
,但我无法解析Category
本身的名称。我尝试了将DBDataRecord
和CurrentValueRecord
转换为EntityKey
的方法,但它只是NULL
。是否有任何方法可以使用ObjectStateManager
来解决这些FKs
到Entities
?
我的完整代码供参考:
专用类SingleMaterialLogger{MaterialAuditData auditData=新材料auditData();public void HandleEntity(ObjectStateEntry e,ObjectContext上下文){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;
}
我遇到了同样的问题。
提取任何具有id值的实体类型并不困难:
DbContext.Set(entityType).Find(id)
但是,这假设您首先已经从相关导航属性中标识了实体类型。这需要一些技巧,基本上是通过使用反射来查看属性名称和[ForeignKey()]属性等来复制EF逻辑。
一些选项包括:
1) 添加智能以从FK ID属性中计算出FK模型属性。然后在审核日志创建过程中动态查找FK模型,并将.ToString()值存储在审核日志中。
这假设:
DataContext/Restore中有一个通用的实用程序,可以动态查找任何模型类型(例如DbContext.Set(entityType).Find(id))
由于以下原因之一,您确信所有FK模型上的.ToString()实现将可靠地工作:
他们从不依赖可能导致运行时错误的进一步导航属性
您可以确信,在您的模型查找中正确地包含了进一步的导航属性()
你已经启用了延迟加载(我强烈建议不要这样……但……这在这里会有所帮助)
您已经考虑了交易的含义(如果您使用的交易超出了EF的范围)
2) 将FK ID存储在审核日志中。然后,在查看审核日志时,动态查找FK模型,并在屏幕上渲染ToString()。
我们在项目中采用了这个选项,效果很好。
但是,您的审核要求可能更严格。例如,如果有人更改FK模型上的名称/描述,则会出现修改旧审核日志的情况。