【问题标题】:ChangeLog OriginalValue same as CurrentValue on EF Core Update EntityChangeLog OriginalValue 与 EF Core 更新实体上的 CurrentValue 相同
【发布时间】:2019-05-21 07:45:20
【问题描述】:

我在在 EF Core 中更新实体时遇到问题,然后在表中记录这些更改。使用的技术有:

  • .NET Core 2.2.0
  • EF Core 2.2.3

所以,我想从数据库中获取一个实体,在前端对其进行编辑,然后在后端对其进行更新,并将这些更改保存到数据库中。最重要的是,我有一个名为 ChangeLogs 的表,其中最重要的字段是 From(映射自 OldValues)和 To(映射自 CurrentValues)。好吧,这两个字段是相同的(意味着它们具有完全相同的值,即新值),情况如下:

  1. 我像这样从数据库中获取实体

    _context.Anomalies .Include(a => a.Asset) .FirstOrDefaultAsync(a => a.Id == anomalyId)

  2. 在前端编辑实体,然后发出PUT请求来更新它;

  3. 更新实体:

    为了让Update() 工作,首先我必须调用它: _context.DetachAllEntities(); 否则我会收到一条错误消息,指出已在跟踪具有相同 ID 的实体。然后拨打Update()和SaveChanges():

    _context.Anomalies.Update(anomaly); _context.SaveChanges();

    anomaly 对象是请求中的对象。

  4. 对于ChangeLog 部分,我在this example 之后重写了SaveChanges() 方法,并且旧/原始和新/当前值设置如下: auditEntry.OldValues[propertyName] = property.OriginalValue; auditEntry.NewValues[propertyName] = property.CurrentValue;

    基本上,这会遍历来自 ChangeTracker 的所有条目,创建一个 AuditEntry 并在 base.SaveChanges() 之后返回并设置该 AuditEntry 的 EntityId,因为您在保存更改之前没有它(这是在如果您添加一个新实体,则保存更改后不会发生任何更新)。

Update() 方法正在工作,更改会反映在数据库中。但唯一的问题是ChangeLogs,来自ChangeTracker.Entries() 的条目不知道OldValues。


我承认我并不完全了解 EF 使用的跟踪系统,但我认为它应该可以帮助我更新实体而不是造成问题。因为我知道打电话给_context.DetachAllEntities();是不正确的。我尝试使用AsNoTracking() 并删除DetachAllEntities(),但结果似乎是一样的。我考虑过使用 dbEntity,将每个字段从请求实体复制到数据库实体,然后复制Update(dbEntity),但这似乎需要做很多工作,基本上是为了一点点好处。我的Anomaly 实体有很多导航属性,很难为其创建复制方法并进行维护。

DetachAllEntities() 是这样定义的:

public static void DetachAllEntities(this DbContext context)
{
    var entries = context.ChangeTracker.Entries().ToList();
    foreach (var entry in entries)
    {
        entry.State = EntityState.Detached;
    }
}

DbContext 设置为 Scoped 生命周期,经理也是。

我也尝试使用this tutorial的审核方法,但结果是一样的。

我担心整个更新过程没有正确完成,因此会出现这个问题..

更新 我创建了一个示例项目来说明这个问题。 You can check the source code on bitbucket。自述文件有更多关于此的信息

我序列化了从上下文中检索到的对象。

左侧有跟踪 右侧没有跟踪

欢迎任何建议、意见、新想法、评论。谢谢!

【问题讨论】:

  • 我认为“DetachEntities”是您问题的根源。分离将从更改跟踪中删除实体,因此原始值和更改值不会更改..
  • 好的。但是我尝试不使用它,但在调用 Update() 时出现错误:“实体已被跟踪”。所以,没有它,我什至无法保存我的更改
  • 值得看看这个库,看看它是如何完成的——或者甚至只是引入它的引用并直接使用它——它可能会让你头疼:github.com/Arch/AutoHistory -为了为您的特定问题提供更具体的答案 - 我们可能需要查看更多代码,在上下文中了解发生了什么
  • 我会看看那个库。非常感谢!另外,我会尽量提供一个小项目,下班后重现问题
  • 问题已更新为示例项目以说明问题

标签: c# entity-framework asp.net-core .net-core entity-framework-core


【解决方案1】:

我认为你的问题在这里。因为这两个对象存在于不同的上下文中,所以它本质上是一个不同的对象。如果您更改代码以便从数据库中检索实体,更新其值,然后使用该对象传递给 SaveChangesAndAudit() 方法。你会得到你期望的结果。我已经调整了随附屏幕截图中的代码。您还将遇到大多数开发人员发现的一个基本问题,那就是映射对象——像 Automapper 这样的工具让你的生活更轻松——所以如果你的实体很大,那么值得一看以提供帮助; -)

https://github.com/AutoMapper/AutoMapper

【讨论】:

  • 基本上,我没有机会,只能将 webEntity 与 dbEntity 逐个字段映射?这太令人沮丧了。我知道自动映射器,但我真的认为可能有更好的解决方案
  • 您需要这样做,因为 EF 无法跟踪更改。它需要在其上下文中包含对象,以便它可以捕获其属性的 Set() 方法并记录更改的内容。只有在这些值发生更改时,才会对其进行跟踪。不幸的是,这就是它的基本原理
  • 我知道这听起来可能不是什么好消息——但它是这个问题的正确答案:-s
  • 非常感谢罗伯特,将尝试您的解决方案并使用自动映射器,如果结果良好,我欠您一个大的 :)
【解决方案2】:

使用 EF 时,会使用“跟踪”信息创建/填充实体,这些信息将实体链接回上下文(和其他事物)。

为了更新实体,您需要做的就是从上下文中查询实体,更改值,然后调用 savechanges。无需分离实体(除非您有特定原因这样做)。

以下示例是更新实体所需的全部内容。

前:

    var anomly = _context.Anomalies
     .Include(a => a.Asset)
     .FirstOrDefaultAsync(a => a.Id == anomalyId);

    anomly.Description = "I have changed the description";

    _context.SaveChanges();

编辑

如果您关闭上下文或分离,则需要在调用 savchanges 之前查询实体并使用新值更新它。

前:

var anomly = _context.Anomalies
 .Include(a => a.Asset)
 .FirstOrDefaultAsync(a => a.Id == anomalyId);

anomly.Description = "I have changed the description";

_context.Dispose();

_context = new DBContext();

var databaseAnomoly = _context.Anomalies
 .Include(a => a.Asset)
 .FirstOrDefaultAsync(a => a.Id == anomaly.Id);

//Update fields
databaseAnomoly.Description = anomly.Description;

_context.SaveChanges();

【讨论】:

  • 我将代码更改为_context.Attach(anomaly); _context.SaveChanges();。还是同样的错误。据我了解,它会尝试遍历异常的所有属性并附加它们,但是如果添加了导航属性,然后在找到具有相同 ID 的实体上,则会弹出错误。即使添加的第一个实体是 Anomaly.Asset 而第二个实体是在 Anomaly.Inspection.Asset 中找到的(例如)
  • 编辑并删除了附加的东西,因为那没有按照我的想法:)
  • 好的,现在我完全明白你想说什么了。但是这里有一个问题,我的实体很大,就像很多属性一样。逐个字段复制将是一场噩梦。我从一开始就考虑过,但这似乎不是一个好的解决方案,更像是一种解决方法
  • 我遇到了类似的问题,有大量字段,并通过重载 + 运算符解决了这个问题。所以现在我们只做 databaseAnomoly += anomly。您最终仍然需要编写所有代码来将字段复制到字段,但您只需执行一次。
  • @Simonca - EF 会做你想做的事,你对它的理解有点偏离。变量太多(请原谅双关语)无法给出正确答案 - 您能否按原样发布您的实际代码,以便我们提供更好的帮助?
【解决方案3】:

问题可能是您的实体最初并非来自数据库,因此 DbContext 不知道属性的旧值。因此,可以通过从数据库中提取实体并更新它来简单地解决这个问题,或者像下面的示例代码那样手动或使用 Automapper。

public async Task<IActionResult> OnPostEditAsync(int id, Product value)  
{  
    var product = dbContext.Products.Find(p => p.Id == id);  

    //Map exisiting product with new values (idealy with Automapper)  
    product.Name = value.Name;  
    product.Price = value.Price;  
    product.Description = value.Description;  

    dbContext.Update(product);  
    await dbContext.SaveChangesAsync();  
}  

【讨论】:

    猜你喜欢
    • 2020-09-04
    • 1970-01-01
    • 1970-01-01
    • 2022-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多