审核实体框架删除了保留以前值的条目

时间:2016-07-19 04:40:53

标签: c# entity-framework audit

我一直在努力解决一个问题,即在他们被保存时审核一般的数据库实体。我有一个使用EF 6的项目,我需要创建一个非侵入性的#34;在实体添加,修改或删除时审核实体的方法。我必须存储插入实体,修改实体或已删除实体的JSON,而不会干扰正常流程。该项目有一个Database First实现。

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

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();
}

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

一切都很完美,我能够将指定状态的实体保存起来:

    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

       }

    }

问题是我丢失了已删除状态中的所有值,我只获取了ID,除了ID之外,属性中没有任何信息,并且没有任何可能以任何方式提取它。我在StackOverflow和其他网站上寻找每一个解决方案,没有什么可以恢复原始信息。 如何将以前删除的值存储起来,就像我存储添加和修改的实体一样?

1 个答案:

答案 0 :(得分:0)

我花了几天时间才弄明白。可能解决方案有点复杂,但我尝试了几个不那么复杂的选项但结果并不好。

首先,因为我只是以不同的方式审核删除,所以我将已删除状态与已添加和已修改分开,但没有任何更改。删除状态是一个特例,我这样对待它。

首先,我需要从数据库中获取原始值。在已删除状态下,他们已经离开,没有任何可能从实体中恢复它们。可以使用以下代码获取它们:

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

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

dbEntityEntry.OriginalValues.SetValues(databaseValues);

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

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());
}

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