提供一个使用Entity Framework Core和SQL数据库的ASP.NET Core网站应用程序。
当尝试更新数据库中的实体时,绝对简单的操作会引发此异常。这是在生产环境中通过错误报告首次注意到的。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("Group")] EditViewModel model)
{
if (id != model.Group.Id) return NotFound();
if (!ModelState.IsValid) return View(model);
_context.Update(model.Group);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
在这行代码_context.Update(model.Group);
处抛出了异常:
InvalidOperationException: 实体类型“Group”的实例无法跟踪,因为已经跟踪了另一个具有相同键的此类型的实例。对于大多数关键类型,如果未设置关键字(即如果为其类型分配默认值),则添加新实体时将创建唯一的临时关键字值。如果您显式地为新实体设置关键字值,请确保它们不与现有实体或为其他新实体生成的临时值发生冲突。在附加现有实体时,请确保只附加具有给定键值的一个实体实例到上下文中。
显然没有其他实例。当我在该行代码上停下断点并展开_context.Group
对象的Results属性时,在我的开发环境中可以重现此异常:
![screenshot of expanded Results property of the _context.Group object](https://istack.dev59.com/vLQMD.webp)
很容易理解,展开Results时,它会加载需要更新的实例,这就是为什么会抛出异常。但是正式部署的生产环境呢?
感谢您的帮助!
UPDATE1 Group
模型:
public class Group
{
[Display(Name = "ID")]
public string Id { get; set; }
public virtual Country Country { get; set; }
[Required]
[Display(Name = "Country")]
[ForeignKey("Country")]
public string CountryCode { get; set; }
[Required]
[Display(Name = "Name")]
public string Name { get; set; }
}
更新2 基于@Mithgroth的答案,我能够重写函数_context.Update()
,使其每次使用时不需要try-catch:
public interface IEntity
{
string Id { get; }
}
public override EntityEntry<TEntity> Update<TEntity>(TEntity entity)
{
if (entity == null)
{
throw new System.ArgumentNullException(nameof(entity));
}
try
{
return base.Update(entity);
}
catch (System.InvalidOperationException)
{
var originalEntity = Find(entity.GetType(), ((IEntity)entity).Id);
Entry(originalEntity).CurrentValues.SetValues(entity);
return Entry((TEntity)originalEntity);
}
}
_context.Group
时,Entity Framework 将查询存储并使用实体填充内存上下文。然后,当您尝试在调用Update
时附加非跟踪实体model.Group
时,它将尝试附加此外部实例,然后会发现与已存在的实体冲突(我假设存在于后备存储(数据库)中)。您是否在请求之间重复使用 Entity Framework 上下文实例? - odyss-jiiservices.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
- Mark SzaboAddDbContext<..>
应该会为每个请求创建一个作用域上下文,所以你的代码应该可以工作。如果你能在开发环境中重现它(不扩展加载整个表的DbSet
),请设置断点并检查_context.Group.Local.Count
属性是否为0
。还有,model.Group
包含什么。顺便问一下,我们能看到Group
实体模型(类)吗?特别是导航属性(有没有可能包含其他Group
对象?) - Ivan Stoevvar group = db.Groups.First(g => g.Id == model.Group.Id); db.Entry(group).CurrentValues.SetValues(model.Group); db.SaveChanges();
- Mithgroth