简体   繁体   English

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

[英]Entity Framework Auditing using ObjectContext on SaveChanges

For audit logging purposes, I need to get values of all the columns, including FK entities and relational entities that have been modifed for one of the table in the database. 为了进行审核日志记录,我需要获取所有列的值,包括已为数据库中的表之一修改的FK实体和关系实体。 Database is basically for a website where user can upload resources (files, online document, picture etc.), I've a table called Material which has multiple many-2-many nd one-2-one relations like Material - Audience , Material - Category , 'Material-Uploader', 'Material-Permission Material -Tags etc. I want to log all the changes happening to a Material . 数据库基本上是一个网站,用户可以在其中上传资源(文件,在线文档,图片等),我有一个名为Material的表,它具有多个2-一对一的关系,例如Material - AudienceMaterial - Category ,“材料上传器”,“材料许可Material -Tags等”。我想记录对Material发生的所有更改。 For example if someone removes a Tag from a Material, then I need to log: 例如,如果有人从物料中删除标签,那么我需要记录:

  • [User12 - 12/12/12] - Happy tag got removed from Crappy material. [User12-12/12/12]- Happy标签已从Crappy材料中移除。

So far I got this: I can get all the ObjectStateEntries which are modified, added, deleted by using: 到目前为止,我明白了:我可以使用以下方法获取所有修改,添加或删除的ObjectStateEntries

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

Now, I can check whether this ObjectStateEntry is RelationShip or not using: 现在,我可以使用以下命令检查此ObjectStateEntry是否为RelationShip:

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

Within HandleEntry method (Entry is not relationship entry), I can check the type of Entry , in my case it is Material , so I'm doing: HandleEntry方法中(Entry不是关系项),我可以检查Entry的类型,在我的情况下,它是Material ,所以我正在做:

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

Once, I know Entry is of type Material Entry , I can get all the columns that have changed for Material table using: 一次,我知道Entry类型为Material Entry ,我可以使用以下方法获取针对Material表更改的所有列:

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

At this point, I can log all the non FK changes of Material table. 此时,我可以记录Material表的所​​有非FK更改。 But if column is FK to some other entity , I cannot resolve that FK value to corresponding Entity . 但是如果column对其他entityFK ,则无法将该FK值解析为对应的Entity I could just know that CategoryID has been changed from 42 to 76 but I cannot resolve name of the Category itself. 我只知道CategoryID已从42更改为76但是我无法解析Category本身的名称。 I tried approach like casting DBDataRecord and CurrentValueRecord to EntityKey but it is just NULL . 我尝试了将DBDataRecordCurrentValueRecord强制DBDataRecordEntityKey但这只是NULL Is there any way to resolve these FKs to Entities using ObjectStateManager ? 是否有任何方法可以使用ObjectStateManager将这些FKs解析为Entities

My full code for the reference: 我的完整代码供参考:

private class SingleMaterialLogger { MaterialAuditData auditData = new MaterialAuditData(); 私有类SingleMaterialLogger {MaterialAuditData auditData = new MaterialAuditData(); public void HandleEntity(ObjectStateEntry e, ObjectContext context) { HandlePrimaryTypeChanges(e); 公共无效HandleEntity(ObjectStateEntry e,ObjectContext context){HandlePrimaryTypeChanges(e); HandleComplexTypeChanges(e, context); 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;
    }

I've run into the same problem. 我遇到了同样的问题。

It's not difficult to pull any entity type with an id value: 提取具有id值的任何实体类型并不难:

DbContext.Set(entityType).Find(id)

However this assumes that you have identified the entity type from the relevant navigation property in the first place. 但是,这首先假设您已从相关导航属性中标识了实体类型。 That requires some smarts, basically duplicating the EF logic by using reflection to look at property names and [ForeignKey()] attributes etc. 这需要一些技巧,基本上是通过使用反射来查看属性名称和[ForeignKey()]属性等来复制EF逻辑。

Some options are: 一些选项是:

1) Add smarts to work out the FK model property from the FK ID property. 1)添加智能以从FK ID属性中计算出FK模型属性。 Then do a lookup of the FK model on the fly during the audit log creation process, and store down the .ToString() value in the audit log. 然后在审核日志创建过程中快速查找FK模型,并将.ToString()值存储在审核日志中。

This assumes: 假设:

  • You have a general utility in your DataContext/Repository to lookup any model type on the fly (eg. DbContext.Set(entityType).Find(id)) 您在DataContext / Repository中有一个通用实用程序,可以即时查找任何模型类型(例如DbContext.Set(entityType).Find(id))

  • You are confident that the .ToString() implementation on all your FK models will work reliably because of one of the following: 您确信由于以下原因之一,所有FK模型上的.ToString()实现都能可靠运行:

    • They never rely on further navigation properties which may cause a run-time error 他们从不依赖于可能导致运行时错误的其他导航属性

    • You can be confident the further navigation properties were properly Include()'ed in your model lookup 您可以确信,进一步的导航属性已在模型查找中正确包含()

    • You have lazy loading enabled (which I strongly advise against.... but.. it would help here) 您启用了延迟加载(强烈建议您这样做.....但这会有所帮助)

  • You have thought through the transaction implications (if you are using transactions beyond what EF does) 您已经仔细考虑了交易的含义(如果您使用的交易超出了EF的范围)

2) Store down the FK ID in the audit log. 2)在审核日志中存储FK ID。 Then, when viewing the audit log, do a lookup of the FK model on the fly and render ToString() on screen. 然后,在查看审核日志时,快速查找FK模型并在屏幕上呈现ToString()。

We went with this option in our project and it works fine. 我们在项目中使用了此选项,并且效果很好。

However your auditing requirements may be stricter. 但是,您的审核要求可能会更严格。 For example, if someone changes the name/description on the FK model, then this will appear to modify the old audit logs. 例如,如果有人更改了FK模型的名称/说明,则这似乎是在修改旧的审核日志。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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