我一直在努力解决一个问题,即在他们被保存时审核一般的数据库实体。我有一个使用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和其他网站上寻找每一个解决方案,没有什么可以恢复原始信息。 如何将以前删除的值存储起来,就像我存储添加和修改的实体一样?
答案 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());
}
可能会有更好的方式,但我认真花了很多时间寻找选择,但我找不到更好的方法。 任何建议或优化都表示赞赏。