我遇到一个问题,需要在EF Core中更新实体,然后将这些更改记录在表中。使用的技术是:
因此,我想从数据库中获取一个实体,在前端进行编辑,然后在后端进行更新,然后将这些更改保存到数据库中。最重要的是,我有一个名为ChangeLogs
的表,其中最重要的字段是From(从OldValues
映射)和To(从CurrentValues
映射)。好吧,这两个字段是相同的(意味着它们具有完全相同的值,即新值),情况如下:
我这样从数据库中获取实体
_context.Anomalies
.Include(a => a.Asset)
.FirstOrDefaultAsync(a => a.Id == anomalyId)
在前端编辑实体,然后发出PUT请求以对其进行更新;
更新实体:
为了使Update()
工作,首先我必须称呼它为:
_context.DetachAllEntities();
否则,我会收到一条错误消息,说已经在跟踪具有相同ID的实体。然后调用Update()
和SaveChanges()
:
_context.Anomalies.Update(anomaly);
_context.SaveChanges();
anomaly
对象是请求中的对象。
对于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()方法正在工作,更改反映在数据库中。但是唯一的问题是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中的Auditing方法,但是结果是相同的。
我担心整个更新过程执行不正确,因此会出现此问题。
更新 我创建了一个示例项目来说明此问题。 You can check the source code on bitbucket。自述文件对此有更多信息
我序列化了从上下文中检索到的对象。
欢迎任何建议,意见,新想法,评论。谢谢!
答案 0 :(得分:2)
我认为您的问题就在这里。由于这两个对象存在于不同的上下文中,因此从根本上来说是一个不同的对象。如果更改代码以便从数据库检索实体,请更新其值,然后使用该对象传递给SaveChangesAndAudit()方法。您将获得预期的结果。我已经调整了所附屏幕截图中的代码。您还将遇到大多数开发人员发现的一个基本问题,那就是映射对象-诸如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();
}