繁体   English   中英

在SaveChanges上使用ObjectContext进行实体框架审核

[英]Entity Framework Auditing using ObjectContext on SaveChanges

为了进行审核日志记录,我需要获取所有列的值,包括已为数据库中的表之一修改的FK实体和关系实体。 数据库基本上是一个网站,用户可以在其中上传资源(文件,在线文档,图片等),我有一个名为Material的表,它具有多个2-一对一的关系,例如Material - AudienceMaterial - Category ,“材料上传器”,“材料许可Material -Tags等”。我想记录对Material发生的所有更改。 例如,如果有人从物料中删除标签,那么我需要记录:

  • [User12-12/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不是关系项),我可以检查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对其他entityFK ,则无法将该FK值解析为对应的Entity 我只知道CategoryID已从42更改为76但是我无法解析Category本身的名称。 我尝试了将DBDataRecordCurrentValueRecord强制DBDataRecordEntityKey但这只是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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM