[英]Entity Framework ObjectContext.SaveChanges() and class methods
[英]Entity Framework Auditing using ObjectContext on SaveChanges
为了进行审核日志记录,我需要获取所有列的值,包括已为数据库中的表之一修改的FK实体和关系实体。 数据库基本上是一个网站,用户可以在其中上传资源(文件,在线文档,图片等),我有一个名为Material
的表,它具有多个2-一对一的关系,例如Material - Audience
和Material - Category
,“材料上传器”,“材料许可Material -Tags
等”。我想记录对Material
发生的所有更改。 例如,如果有人从物料中删除标签,那么我需要记录:
Happy
标签已从Crappy
材料中移除。 到目前为止,我明白了:我可以使用以下方法获取所有修改,添加或删除的ObjectStateEntries
:
context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified)
现在,我可以使用以下命令检查此ObjectStateEntry
是否为RelationShip:
if (e.IsRelationship) {
HandleRelationshipEntry(e);
}
else {
HandleEntry(e);
}
在HandleEntry
方法中(Entry不是关系项),我可以检查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更改。 但是如果column对其他entity
是FK
,则无法将该FK
值解析为对应的Entity
。 我只知道CategoryID
已从42
更改为76
但是我无法解析Category
本身的名称。 我尝试了将DBDataRecord
和CurrentValueRecord
强制DBDataRecord
为EntityKey
但这只是NULL
。 是否有任何方法可以使用ObjectStateManager
将这些FKs
解析为Entities
?
我的完整代码供参考:
私有类SingleMaterialLogger {MaterialAuditData auditData = new MaterialAuditData(); 公共无效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;
}
我遇到了同样的问题。
提取具有id值的任何实体类型并不难:
DbContext.Set(entityType).Find(id)
但是,这首先假设您已从相关导航属性中标识了实体类型。 这需要一些技巧,基本上是通过使用反射来查看属性名称和[ForeignKey()]属性等来复制EF逻辑。
一些选项是:
1)添加智能以从FK ID属性中计算出FK模型属性。 然后在审核日志创建过程中快速查找FK模型,并将.ToString()值存储在审核日志中。
假设:
您在DataContext / Repository中有一个通用实用程序,可以即时查找任何模型类型(例如DbContext.Set(entityType).Find(id))
您确信由于以下原因之一,所有FK模型上的.ToString()实现都能可靠运行:
他们从不依赖于可能导致运行时错误的其他导航属性
您可以确信,进一步的导航属性已在模型查找中正确包含()
您启用了延迟加载(强烈建议您这样做.....但这会有所帮助)
您已经仔细考虑了交易的含义(如果您使用的交易超出了EF的范围)
2)在审核日志中存储FK ID。 然后,在查看审核日志时,快速查找FK模型并在屏幕上呈现ToString()。
我们在项目中使用了此选项,并且效果很好。
但是,您的审核要求可能会更严格。 例如,如果有人更改了FK模型的名称/说明,则这似乎是在修改旧的审核日志。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.