我们正在使用EF Core6,目前看到PROD中的一个API偶尔会抛出DbUpdateConcurrencyException
.到目前为止,我们还没有实现逻辑(并发判断/RowVersion)来管理乐观并发.我想在调试时重现相同的错误,所以我遵循了以下步骤
- 已加载项目列表
- 已手动更新数据库(SQL Server)上的列
- 从代码调用
SaveChanges()
令人惊讶的是,这在调试时没有引发任何错误.我想知道为什么这些并发更新会在Prod中导致异常,而不是在调试时?
我们正在使用EF Core6,目前看到PROD中的一个API偶尔会抛出DbUpdateConcurrencyException
.到目前为止,我们还没有实现逻辑(并发判断/RowVersion)来管理乐观并发.我想在调试时重现相同的错误,所以我遵循了以下步骤
SaveChanges()
令人惊讶的是,这在调试时没有引发任何错误.我想知道为什么这些并发更新会在Prod中导致异常,而不是在调试时?
如果表没有设置并发令牌,则不会收到更新并发错误(即过时数据),但会收到与删除相关的删除并发错误.以下是报告"数据库操作预期影响1行(S),但实际影响0行(S)"的错误
您可以通过编辑现有实体来重现此过程,然后在数据库中删除该实体,然后呼叫SaveChanges
.收到此错误可能会令人困惑,因为当您没有您认为会删除行的代码时,可能会发生此错误.
如果您的系统确实删除了有多个活动会话正在进行的行,则需要处理此问题.如果您在不应该删除行的时候收到此消息,则可能导致此类问题的一个常见错误是设置集合导航属性.
例如,假设一个博客实体有一个与之相关联的标签列表,并且您有一个屏幕来添加或删除博客上的相关标签.代码如下:
var blog = _context.Blogs
.Include(x => x.Tags)
.Single(x => x.BlogId == blogId);
blog.Tags = updatedTags;
...是很糟糕的.Setter将用于单个导航属性,但绝不应用于集合.在集合中单独添加和删除相关标签,因为EF的更改跟踪器绑定到在阅读博客条目时设置的标签集合.
其他要判断的罪魁祸首是可能遗漏了警告的无意编辑,这些警告可能已经污染了DbContext.
例如,您可能有一行代码在某处执行判断,如下所示:
if (blog.Status = Statuses.Draft)
不是:
if (blog.Status == Statuses.Draft)
编译器将在第一条语句中生成一条关于潜在意外赋值的警告,但如果您的解决方案已经有几十个您习惯于忽略的编译器警告,那么在您不打算更新博客的地方可能会错过这个警告,当保存一些不相关的东西时,它会try 更新博客或其他您不期望的实体,导致您徒劳无功.帮助避免这种情况的有用步骤:
AsNoTracking()
或Projection( Select 匿名类型或DTO/ViewModel),而不是读取跟踪的实体.对于要解决的意外赋值,您仍然会有一个"错误",但它不会 destruct DbContext.使用调试器跟踪这些受污染的实体之一可能有点棘手,但如果您碰巧用可重现的示例中断了异常,请查看更改跟踪器以判断DbContext正在try 更新哪些实体.你可能会发现一个你没想到会看到的实体.至于生产,您可能会有一个分析器捕获数据库上的SQL语句,然后遍历所有内容,执行更新,并找到导致没有更新的行的内容.