【问题标题】:Overloading SaveChanges to preserve prior values重载 SaveChanges 以保留先前的值
【发布时间】:2015-01-09 16:22:04
【问题描述】:

我正在尝试在我的 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

从概念上讲,当我不一定知道实体是什么类型(即我将更改保存到哪个表)时,我正在努力解决如何在实体中创建新记录。

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 As DbPropertyValues = Current”有关吗?我应该以某种方式实例化一个新的 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

宾果! - 这行得通。

【问题讨论】:

  • 所以历史记录总是只有一个?如果是这样,那作为审计跟踪几乎没有用。
  • 否 - 永远不会删除记录 - 将记录表所处的每个状态,但只有一个的“状态”为“当前”。进行更新时,“当前”记录设置为“旧”(连同所有先前版本)并创建一个新记录。
  • 好的。或许CurrentValues.ToObject()可以帮到你。
  • 好的,非常感谢!我认为这是我需要将值从旧的“更新”行转移到全新的“当前”行。我已经为我的问题添加了更新,因为我仍然没有完全了解我所追求的行为

标签: vb.net entity-framework


【解决方案1】:

我认为你不想走这条路。首先想到的问题是如何处理实体关系(一对多、一对一或多对多)?

您应该使用DbContext.Set 方法获取DbSet,然后调用DbSet.Create 让它创建实体的实例,而不是执行Activator.CreateInstance。


您可以使用类似于以下代码的内容将修改后的实体的原始值复制到新的“审核”实体中。我创建了一个名为“Hi”的实体并保存它。保存后,我更改名称并创建一个“审核”对象来存储原始值。

另外,在过去,我通过使用触发器将修改行的原始记录复制到不同的表来完成审计。例如:我将有一个名为 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();

}

【讨论】:

  • 谢谢 - 是的,这听起来更有希望。但是那我如何更新未知实体类型的字段呢?我想做类似的事情: Dim t As Type = entry.Entity.GetType Dim EntityDBSet = Me.Set(t) Dim NewRow = EntityDBSet.Create NewRow("Status").value="Current" '
  • 非常感谢! - 是的,我需要将原始值写回当前值并将状态设置为“旧”。我已经更新了我的问题,因为现在更新的旧记录和新记录都没有保存到数据库中,我认为您的回答表明它们应该保存?
猜你喜欢
  • 2021-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多