ChangeLog OriginalValue与EF Core Update实体上的CurrentValue相同

时间:2019-05-21 07:45:21

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

我遇到一个问题,需要在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()方法,并且Old / Original和New / Current值设置如下: auditEntry.OldValues[propertyName] = property.OriginalValue; auditEntry.NewValues[propertyName] = property.CurrentValue;

    基本上,这会遍历ChangeTracker中的所有条目,创建一个AuditEntry,在base.SaveChanges()之后,它将返回并设置该AuditEntry的EntityId,因为您之前没有它保存更改(这是在您添加新实体的情况下,要进行更新,保存更改后什么也不会发生)。

Update()方法正在工作,更改反映在数据库中。但是唯一的问题是ChangeLogsChangeTracker.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中的Auditing方法,但是结果是相同的。

我担心整个更新过程执行不正确,因此会出现此问题。

  

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

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

在左侧跟踪时<====>在右侧没有跟踪时 With Tracking on the LEFT <====> With NO tracking on the RIGHT

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

3 个答案:

答案 0 :(得分:2)

enter image description here

我认为您的问题就在这里。由于这两个对象存在于不同的上下文中,因此从根本上来说是一个不同的对象。如果更改代码以便从数据库检索实体,请更新其值,然后使用该对象传递给SaveChangesAndAudit()方法。您将获得预期的结果。我已经调整了所附屏幕截图中的代码。您还将遇到大多数开发人员发现的一个基本问题,那就是映射对象-诸如Automapper之类的工具通过这种事情使您的生活更轻松-因此,如果您的实体很大,那么值得一看,以助您一臂之力; -)

https://github.com/AutoMapper/AutoMapper

答案 1 :(得分:1)

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

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

  

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

EX:

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

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

    _context.SaveChanges();

编辑

  

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

EX:

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();

答案 2 :(得分:0)

问题可能是您的实体最初不是来自数据库,所以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();  
}