【问题标题】:Audit Entity Framework deleted entries preserving previous value审计实体框架删除的条目保留以前的值
【发布时间】:2016-11-21 19:51:48
【问题描述】:

我一直在努力解决一个问题,即在保存数据库实体时对其进行一般审计。我有一个使用 EF 6 的项目,我需要创建一种“非侵入性”方法来在添加、修改或删除实体时对其进行审计。我必须在不干扰正常流程的情况下存储插入实体、修改实体或删除实体的 JSON。该项目有一个 Database First 实现。

我的解决方案很简单,添加其他程序员想要审核实施 IAudi 的任何实体的部分类,这基本上是一个空接口,用于从实现它的实体获取所有更改。

public interface IAudit {}

我有一个 Currencies 实体,它只是在没有任何其他代码的情况下实现它(我将来可以做其他事情,但我不需要它)

public partial class Currencies : IAudit

我重写 SaveChanges 方法来查找要审计的实体

public override int SaveChanges()
{
    ChangeTracker.DetectChanges();

    // This linq looks for new entities that were marked for audit
    CreateAuditLog(System.Data.Entity.EntityState.Added);
    CreateAuditLog(System.Data.Entity.EntityState.Modified);
    CreateAuditLog(System.Data.Entity.EntityState.Deleted);

    return base.SaveChanges();
}

该解决方案调用了 3 次 CreateAuditLog,因为在不久的将来我需要实施一个配置来审核用户决定的任何内容,可能来自用户激活/停用的数据库配置。

一切都很顺利,我能够在指定状态下保存实体:

    private void CreateAuditLog(System.Data.Entity.EntityState state)
    {
        var auditedEntities = ChangeTracker.Entries<IAudit>()
            .Where(p => p.State == state)
            .Select(p => p.Entity);

       ... some code that do something else

       foreach (var auditedEntity in auditedEntities)
       {
          ... some information I required to add

         strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter());

          ... some code to save audit information

       }

    }

问题是我在 Deleted 状态下丢失了所有值,我只获得了 ID,除了 ID 之外,属性中没有任何信息,并且不可能以任何方式提取它。我在 StackOverflow 和其他网站上寻找了每一个解决方案,但没有任何东西可以恢复原始信息。 如何获取以前删除的值以存储它们的方式与存储已添加和已修改实体的方式相同?

【问题讨论】:

  • 我一直在研究一个可能会有所帮助的库。看看Audit.EntityFramework 库,它会拦截 SaveChanges() 并且可以配置为过滤您要审核的实体。

标签: c# entity-framework audit


【解决方案1】:

我花了几天时间才弄明白。可能是解决方案有点复杂,但我尝试了几个不太复杂的选项,但效果不佳。

首先,由于我只是以不同的方式审核删除,因此我将已删除状态与已添加和已修改分开,它们运行良好,没有任何变化。删除状态是一种特殊情况,我会这样对待。

首先,我需要从数据库中获取原始值。在已删除状态下,它们已经消失,不可能从实体中恢复它们。可以通过以下代码获取它们:

var databaseValues = this.Entry(auditedEntity).GetDatabaseValues();

结果只是 DB 属性值 (DbPropertyValues) 的集合。如果我能得到原始值,我会从已删除的实体中设置原始值:

dbEntityEntry.OriginalValues.SetValues(databaseValues);

这一行只填充实体的原始值,根本不修改当前值。这样做很有用,因为它需要一些代码来检查每个属性并自己设置它,这是一个有趣的快捷方式。 现在,问题是我没有要序列化的实体,所以我需要一个新的实体,在我的情况下我是通过反射创建的,因为我不知道类型(我收到实现 IAudi 的实体)

Type type = auditedEntity.GetType();
var auditDeletedEntity = Activator.CreateInstance(type);

这是我稍后将序列化以存储审计的实体。

现在,复杂的部分,我需要获取实体属性并通过实体中设置的原始值的反射来填充它们:

foreach (var propertyInfo in type.GetProperties())
{
    if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType)
    {
        var propertyValue = originalValues.GetValue<object>(propertyInfo.Name);
        auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name,
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
            Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue });
    }
}

我必须检查泛型和数组类型以避免遵循不适用于此方法的 EF 关系,我也不需要(我需要对象而不是整个树)

之后我只需要序列化被审计的删除实体:

strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter());

代码如下所示:

string strJSON = string.Empty;
if (state == System.Data.Entity.EntityState.Deleted)
{
    var databaseValues = this.Entry(auditedEntity).GetDatabaseValues();

    // Get original values from the database (the only option, in the delete method they're lost)
    DbEntityEntry dbEntityEntry = this.Entry(auditedEntity);
    if (databaseValues != null)
    {
        dbEntityEntry.OriginalValues.SetValues(databaseValues);
        var originalValues = this.Entry(auditedEntity).OriginalValues;
        Type type = auditedEntity.GetType();
        var auditDeletedEntity = Activator.CreateInstance(type);
        // Get properties by reflection
        foreach (var propertyInfo in type.GetProperties())
        {
            if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType)
            {
                var propertyValue = originalValues.GetValue<object>(propertyInfo.Name);
                auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name,
                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
                    Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue });
            }
        }
        strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter());
    }
}
else
{
    strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter());
}

可能有更好的方法,但我认真地花了很多时间寻找选项,但找不到更好的方法。 任何建议或优化表示赞赏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-24
    • 1970-01-01
    • 2016-01-17
    • 2011-04-18
    • 2017-04-03
    • 2013-10-31
    • 1970-01-01
    相关资源
    最近更新 更多