我正在尝试在dbContext中的各种表中实现各种类型的审计跟踪。
我不希望任何更改被SaveChanges覆盖,我希望将现有记录标记为“旧”'并生成标记为“当前”的新记录。 (与进行更改的用户一起记录)。因此,这给了我一种各种审计线索。
我目前的做法是覆盖/重载SaveChanges,然后使用ChangeTracker条目:
Public Overrides Function SaveChanges() As Integer
Throw New InvalidOperationException("User ID must be provided")
End Function
Public Overloads Function SaveChanges(userID As String) As Integer
Dim recordsUpdated as integer=0
For Each entry As dbentityentry In Me.ChangeTracker.Entries().Where(Function(e) e.State <> EntityState.Unchanged)
Dim Original As DbPropertyValues = entry.OriginalValues
Dim Current As DbPropertyValues = entry.CurrentValues
Dim t As Type = entry.Entity.GetType
Dim x = Activator.CreateInstance(t) 'create new entity of same type as original
x=.....
' Am stuck here... I'm trying to create a new record of the same entity type as the original
'which I will then append to the dbContext/entity, set a field as 'current' and save.
'Meanwhile I'll change the old field's status to 'old' and MyBase.SaveChanges that.
Next 'loop through the changes
'record how many fields were different/had changed
For Each PropertyName In Original.PropertyNames
Dim OriginalValue As Object = Original.GetValue(Of Object)(PropertyName)
Dim CurrentValue As Object = Current.GetValue(Of Object)(PropertyName)
If Not Object.Equals(OriginalValue, CurrentValue) Then
recordsUpdated+=1
End If
Next
Next
MyBase.SaveChanges()
'Save journal entries
Return recordsUpdated
End Function
从概念上讲,当我不知道实体是什么类型(即我保存哪个表格更改)时,我一直在努力如何在一个实体中创建一个新记录。< / p>
Activator.CreateInstance是正确的方法吗?或者我是否必须对每个实体进行测试并对新记录进行硬编码?
更新1
感谢hjb和Gert,我认为我在正确的路线上 - 我只是没有看到我期待的行为。现在重载的SaveChanges是:
Public Overloads Function SaveChanges(userID As Integer) As Integer
Dim recordsUpdated As Integer = 0
For Each entry As dbentityentry In Me.ChangeTracker.Entries().Where(Function(e) e.State <> EntityState.Unchanged)
Dim Original As DbPropertyValues = entry.OriginalValues
Dim Current As DbPropertyValues = entry.CurrentValues
Dim NewValues As DbPropertyValues = Current 'NewValue will go into the brand new record (with status='current')
Dim t As Type = entry.Entity.GetType
Dim EntityDBSet = Me.Set(t)
entry.CurrentValues.SetValues(entry.OriginalValues) '<--we don't actually want to change the old record other than set status to 'old'
Dim NewRow = EntityDBSet.Create 'here's our new row created
NewValues("Creator") = Convert.ToInt64(userID)
NewValues("Status")="Current" '<-- indicates the new current data item
Current("Status")="Old" '<-- indicates it's a previous version
NewRow = NewValues.ToObject '<--- put the NewValues into a row object to add to table
'Now append the newly created row
EntityDBSet.Add(NewRow)
Next
MyBase.SaveChanges() 'save the old row (now with status 'old') together with the newly created row (with status 'current')
Return recordsUpdated
End Function
我已经删除了记录的计数,只是为了使其更简洁。
我看到添加了一个新行,但它只是旧行的副本 - 没有对旧版“当前”行进行任何更改。行和新行没有反映在调用原始SaveChanges之前对数据所做的任何更改。
可能与“Dim NewValues DimPropertyValues = Current&#39; Dim NewValues有关。 - 我应该以某种方式实例化一个新的NewValues吗?如果是这样的话?
更新2 - 已解决!
我怀疑我需要NewValues的新对象,当然实例化的方法是使用.ToObject。所以我有:
Public Overloads Function SaveChanges(userID As Integer) As Integer
Dim recordsUpdated As Integer = 0
For Each entry As dbentityentry In Me.ChangeTracker.Entries().Where(Function(e) e.State <> EntityState.Unchanged)
Dim Original As DbPropertyValues = entry.OriginalValues
Dim Current As DbPropertyValues = entry.CurrentValues
Dim NewValues = Current.ToObject 'NewValue will go into the brand new record (with status='current')
Dim t As Type = entry.Entity.GetType
Dim EntityDBSet = Me.Set(t)
entry.CurrentValues.SetValues(entry.OriginalValues) '<--we don't actually want to change the old record other than set status to 'old'
Current("Status")="Old" '<-- indicates it's a previous version
Dim NewRow = EntityDBSet.Create 'here's our new row created
NewValues("Creator") = Convert.ToInt64(userID)
NewValues("Status")="Current" '<-- indicates the new current data item
NewRow = NewValues.ToObject '<--- put the NewValues into a row object to add to table
'Now append the newly created row
EntityDBSet.Add(NewRow)
Next
MyBase.SaveChanges() 'save the old row (now with status 'old') together with the newly created row (with status 'current')
Return recordsUpdated
End Function
宾果! - 这很有效。
答案 0 :(得分:1)
我认为你不想走这条路。想到的第一个问题是如何处理实体关系(一对多,一对一或多对多)?
您应该使用DbContext.Set方法获取DbSet,然后调用DbSet.Create以使其创建实体的实例。
而不是使用Activator.CreateInstance。您可以使用与以下代码类似的内容将修改后的实体的原始值复制到新的“审核”实体。我创建一个名为“Hi”的实体并保存。保存后,我更改名称并创建一个“审核”对象来存储原始值。
此外,在过去,我通过使用触发器将修改行的原始记录复制到另一个表来完成审核。 E.x。:我将有一个名为CompanyHistory的表,触发器会将Company表中已修改行的原始值复制到CompanyHistory表中。它还有一些列来存储谁进行了更改,何时,从什么机器和更改的时间。我使用SMO库生成c#中的大部分触发器。
using (var dbContext = new MyDbContext())
{
dbContext.Configuration.ProxyCreationEnabled = true;
dbContext.Configuration.AutoDetectChangesEnabled = true;
dbContext.Configuration.LazyLoadingEnabled = true;
var company = dbContext.Companies.Add(dbContext.Companies.Create());
company.name = "HI";
dbContext.SaveChanges();
company.name = "dfsf";
var updatedEntities = dbContext.ChangeTracker
.Entries()
.Where(obj => obj.State == System.Data.Entity.EntityState.Modified)
.ToArray();
foreach (var updatedEntity in updatedEntities)
{
var dbSet = dbContext.Set(updatedEntity.Entity.GetType());
var auditEntity = dbSet.Add(dbSet.Create());
var auditEntityEntry = dbContext.ChangeTracker.Entries().Where(obj => obj.Entity == auditEntity).First();
auditEntityEntry.CurrentValues.SetValues(updatedEntity.OriginalValues);
}
dbContext.SaveChanges();
}