【问题标题】:Entity Framework DbContext SaveChanges() OriginalValue Incorrect实体框架 DbContext SaveChanges() OriginalValue 不正确
【发布时间】:2012-03-24 04:56:43
【问题描述】:

我正在尝试使用 EF 4.1 实现 AuditLog,方法是覆盖以下位置讨论的 SaveChanges() 方法:

我在“修改”条目方面遇到了问题。每当我尝试获取相关属性的 OriginalValue 时,它始终具有与 CurrentValue 字段中相同的值。

我第一次使用这段代码,它成功识别出被修改的条目:

public int SaveChanges(string userID)
{

    // Have tried both with and without the following line, and received same results:
    // ChangeTracker.DetectChanges();

    foreach (
      var ent in this.ChangeTracker
                     .Entries()
                     .Where( p => p.State == System.Data.EntityState.Added ||
                                     p.State == System.Data.EntityState.Deleted ||
                                     p.State == System.Data.EntityState.Modified ))
    {
        // For each change record, get the audit record entries and add them
        foreach (AuditLog log in GetAuditRecordsForChange(ent, userID))
        {
            this.AuditLog.Add(log);
        }

    }

    return base.SaveChanges();
}

问题出在这个(缩写代码):

    private List<AuditLog> GetAuditRecordsForChange(DbEntityEntry dbEntry, string userID)
    {
        if (dbEntry.State == System.Data.EntityState.Modified)
        {
            foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
            {
                if (!object.Equals(dbEntry.OriginalValues.GetValue<object>(propertyName),
                    dbEntry.CurrentValues.GetValue<object>(propertyName)))
                {
                        // It never makes it into this if block, even when
                        //    the property has been updated.
                }

                // If I updated the property "Name" which was originally "OldName" to the value "NewName" and then break here and inspect the values by calling:
                //      ?dbEntry.OriginalValues.GetValue<object>("Name").ToString()

                // the result will be "NewName" and not "OldName" as expected
             }
         }
    }

奇怪的是对dbEntry.Property(propertyName).IsModified();的调用会 在这种情况下返回 true。只是 OriginalValue 里面没有预期值。有人愿意帮助我指出正确的方向吗?我似乎无法让它正常工作。

【问题讨论】:

  • 您是如何查询实体然后更改属性值的?如果您实质上是附加实体,然后将状态设置为已修改,那么原始值将丢失。要保留原始值,您需要让 EF 从查询到保存一直跟踪实体,或者您需要在自己的代码中跟踪原始值。
  • 抱歉 - 我试图在 cmets 中发布一些代码,但效果不佳。我正在使用 MVC 控制器的 [HttpPost] 操作。这调用了我的产品存储库的“SaveProduct”方法。在存储库中,看起来我确实调用了context.Entry(product).State = EntityState.Modified,然后我在上下文中保存了更改。您能否向我指出演示您提到的两种技术的任何资源?或者至少给我一些指点?
  • 认为在 MVC 中推荐的方法是使用隐藏字段。本质上,您会将您关心的原始值保存到隐藏字段中,然后稍后在您的 POST 中读回它们。我不知道这方面的最佳实践,或者是否有 MVC 抽象来提供帮助。
  • 特别是,我想知道如何“让 EF 从查询到保存一直跟踪实体”。我认为这已经发生了。当我进一步调查时,我发现在存储库中,就在我调用 context.Entry(product).State = EntityState.Modified 之前,我可以看到原始值(“OldValue”)。但是,只要我调用那行代码,DBContext 中感兴趣的“产品”就会将其OriginalValueCurrentValue 设置为“NewValue”。所以,我看你是对的,但我不知道如何以 EF 将跟踪的方式更新它。
  • 在我最近发表评论之前,我没有看到您的第二篇文章。我真的很感谢你在这方面的帮助。你认为我现在应该创建另一个关于在 MVC 中执行此操作的首选方式的 SO 问题吗?

标签: c# entity-framework entity-framework-4.1 ef-code-first


【解决方案1】:

当 EF 从数据库中检索实体时,它会为该实体的所有属性拍摄原始值的快照。稍后,随着对这些属性的值进行更改,原始值将保持不变,而当前值会发生变化。

但是,要做到这一点,EF 需要在整个过程中跟踪实体。在 Web 或其他 n 层应用程序中,通常会将值发送到客户端,并释放用于查询实体的上下文。这意味着实体现在不再被 EF 跟踪。这是很好的做法。

一旦应用程序回发,实体就会使用来自客户端的值进行重构,然后重新附加到上下文并设置为已修改状态。但是,默认情况下,从客户端返回的唯一值是当前值。原始值丢失。通常这无关紧要,除非您正在执行乐观并发或者想要非常小心地只更新真正改变的值。在这些情况下,原始值也应该发送到客户端(通常作为 Web 应用程序中的隐藏字段),然后作为附加过程的一部分重新应用为原始值。这在上面的示例中没有发生,这就是原始值未按预期显示的原因。

【讨论】:

  • 再次感谢您的帮助。我现在已经按预期工作了……我想。只是为了澄清:您是说调用 context.Entry(product).State = EntityState.Modified 将“产品”重新附加到上下文,因此它失去了对其 OriginalValues 的跟踪?另外here是我后续发的新帖(仅供参考)。
  • 但是为什么@JoeDePung 问题中链接的更改跟踪示例中的方法有效?
  • 他正在覆盖 SaveChanges(),我假设他在将数据返回给客户端和处理上下文之前调用它。
【解决方案2】:

如果你改变了

dbEntry.OriginalValues.GetValue<object>(propertyName);

dbEntry.GetDatabaseValues().GetValue<object>(propertyName);

那就行了。

【讨论】:

  • 有效,但对于审计目的来说可能非常昂贵。
  • 为什么非常膨胀?如果你想知道变化前后的值就得去查询数据库,没办法。
  • 它有效.. 但是第一个不保持原始值的原因是什么以及为什么与 currentvalue @eoleary 相同
【解决方案3】:

当我在上下文中覆盖 SaveChanges 时出现此错误如下

public override int SaveChanges()
    {
        var changeInfo = ChangeTracker.Entries()
            .Select(t => new {
                Original = t.OriginalValues.PropertyNames.ToDictionary(pn => pn, pn => t.OriginalValues[pn]),
                Current = t.CurrentValues.PropertyNames.ToDictionary(pn => pn, pn => t.CurrentValues[pn]),
            }).ToList();
        return base.SaveChanges();
    }

当我清除它时修复了!

SaveChanges 中的ChangeTracker.Entries().ToList() 错误...

【讨论】:

    【解决方案4】:

    问题不在于您在此处显示的代码。问题在于您如何跟踪实体。 如果您只是创建一个实体对象并在其上调用 Update EF 框架,只需覆盖 db 中的现有值(前提是您提供了正确的 ID)。这样做是为了提高效率。所以如果你这样做:

    var company = new Company{Id = mySuppliedId, Name = newname};
    Context.Companies.Update(company);
    Context.SaveChanges(); 
    

    EF 将直接进入 DB 并更新实体上的所有属性,而不会先带回任何东西。所以它无法知道原始值。 如果您将逻辑中的代码更改为:

    var company = Context.Companies.Where(c=>c.Id == mySuppliedId).FirstOrDefault();
    company.Name = newName;
    Context.SaveChanges() 
    

    然后您在上面显示的 ChangeTracker 代码突然开始工作,因为 EF 首先从数据库中获取数据。但是,当您进行额外查询时,效率会降低。

    【讨论】:

      【解决方案5】:

      我需要 post 方法中的旧/原始值。最后这对我有用。

      //Get Orignal value before save changes
            Item entityBeforeChange = db.Items.Single(x => x.Id == item.Id);
            db.Entry(entityBeforeChange).State = EntityState.Detached; // breaks up the connection to the Context
            var locId = entityBeforeChange.LocationId;//Orignal value
      
            //Saving the current Value
            if (ModelState.IsValid)
            {
              db.Entry(item).State = EntityState.Modified;
              await db.SaveChangesAsync();
              return RedirectToAction("Index");
            }
      

      【讨论】:

        【解决方案6】:

        您可以获取尚未提交的数据。

        var Current = _dbContext.Entry(entity).GetDatabaseValues().ToObject();
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-10-15
          • 2016-01-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-05-09
          相关资源
          最近更新 更多