实体框架: "存储更新、插入或删除语句影响了意外数量的行(0)。"

354
我正在使用Entity Framework填充网格控件。有时在进行更新时,会出现以下错误:

存储更新、插入或删除语句影响了意外数量的行数 (0)。实体可能已经被修改或删除自从实体被加载以来。刷新ObjectStateManager条目。

我无法想出如何重现这个问题。但这可能与我更新的时间间隔有关。有人见过这个问题吗?或者有人知道这个错误信息是什么意思吗?
编辑:很遗憾,我不能再重现我在这里所遇到的问题,因为我离开了这个项目,并且不记得是否最终找到了解决方案,是否有其他开发人员修复了它,或者我是否解决了它。因此,我无法接受任何答案。

在引入了一个[SQL Server行级安全策略,允许将一行更新到无法读取的状态(具有宽容的BLOCK谓词的独占FILTER谓词)](https://stackoverflow.com/q/50009638/263832)后,我遇到了这个错误。EntityFramework要求在更新后读取更新的行,否则它会认为这是一个并发错误(至少在使用乐观并发时)。 - xr280xr
问题可能是您的DBContext作用域不正确 https://dev59.com/zKnka4cB1Zd3GeqPQ5w-(此示例适用于ASPNET Identity,但适用于任何上下文) - Simon_Weaver
无论出现错误的上下文是什么,将断点设置在实例化上下文的位置通常都是一个好主意。您是否期望在加载网页时只实例化一次,但它却命中了 5 次断点?那么您可能遇到了竞态条件。查看“Request.Uri”以查看实际请求 URL。在我的情况下,我有一些跟踪逻辑正在访问我的站点,并且不必要地从数据库加载上下文(有时还会更新上下文)。因此,我正在调试的实际页面的数据被愚蠢的跟踪代码逻辑覆盖。 - Simon_Weaver
在视图中添加 @Html.AntiForgeryToken()。 - Vikas Sharma
我现在无法回答这个问题,但是当我使用反向 poco 生成器(https://marketplace.visualstudio.com/items?itemName=SimonHughes.EntityFrameworkReversePOCOGenerator) 时,我遇到了这个错误,问题是我没有在表上设置主键。将主键添加到表中并更新模型可以解决我的问题。 - Saurabh Solanki
45个回答

420
我遇到了这个问题,原因是实体的ID(键)字段未设置。因此,当上下文尝试保存数据时,它找不到ID=0。请确保在更新语句中设置了断点,并验证实体的ID是否已设置。
根据Paul Bellora的评论:

我也遇到过这个问题,原因是忘记在.cshtml编辑页面中包含隐藏的ID输入


3
+1 我遇到了同样的问题,这个帮助我找到了解决办法。原来我的Order模型里有[Bind(Exclude = "OrderID")],这导致在HttpPost时实体的ID值为零。 - Dhaust
3
正是我所缺少的。我的对象ID为0。 - Azhar Khorasany
5
@Html.HiddenFor(model => model.productID) -- 工作得很好。我在编辑页面(MVC RAZOR)上缺少了productID。 - Ravi Ram
2
我遇到了类似的问题,但有些不同。对我来说,问题在于我没有正确设置SQL表。我的主键字段没有设置为自动递增。因此,EF会发送我尝试插入的记录,但没有关键字,如果您记得告诉SQL该字段是自动递增标识字段,那就没问题了,但我忘了 :< - Agile Noob
1
相同的问题,但使用复合键。其中一个关键值未设置。 - obaylis
显示剩余10条评论

219

这是一种被称为乐观并发的功能带来的副作用。

虽然不确定如何在Entity Framework中启用/禁用它,但基本上它告诉你的是,在你从数据库中获取数据和保存更改之间,有人更改了数据(这意味着当你去保存它时,实际上没有更新任何行)。用SQL术语来说,他们的update查询的where子句包含了行中每个字段的原始值,如果影响0行,则知道出了问题。

其背后的想法是,你不会覆盖你的应用程序不知道已经发生了变化的更改 - 它基本上是.NET在所有更新中添加的一项小安全措施。

如果它是一致的,可能是发生在你自己的逻辑内(例如:在选择和更新之间的另一个方法中实际上更新了数据),但也可能只是两个应用程序之间的竞争条件。


41
这件事情发生在单用户环境中(在我的开发机器上),所以我认为这不可能是竞态条件。我正在使用一个自定义的网格控件和一个EntityDataSource进行绑定,所以我不确定背后发生了什么,但我没有任何额外的代码修改表格。是否有一种方法可以更改此并发设置? - strongopinions
3
我认为您可以在实体模型中的每列上进行设置(在属性窗口中),但问题是这只会防止您看到错误,而且它仍然不能更新任何内容。您能否查看发送到数据库的SQL命令(例如:MSSQL的SQL Server Profiler)?这样,您就可以看到生成的更新以及为什么该更新不影响任何行。 - fyjham
9
如果实体具有时间戳属性,请确保您已将其存储在视图中,并确保实体正确填充时间戳。 - anIBMer
3
如果您使用时间戳,想要删除的对象需要设置主键和行版本属性才能成功更新!我在将对象附加到相应的DbSet后设置了行版本(时间戳)属性,这就是为什么它没有起作用的原因。做得好! - Legends
我遇到了这个问题,并发现每个更新语句后面都会跟随一个带有where子句@@ROWCOUNT > 0的select语句。如果你有一个时间戳,你需要在更新语句中包含时间戳,否则@@ROWCOUNT将为零。 - code4j
显示剩余3条评论

121

哇,很多答案,但当我尝试一些别人没有提到过的东西时,我遇到了这个错误。

长话短说,如果你创建了一个新对象并使用EntityState.Modified告诉EF它被修改了,那么它会抛出此错误,因为它还不存在于数据库中。以下是我的代码:

MyObject foo = new MyObject()
{
    someAttribute = someValue
};

context.Entry(foo).State = EntityState.Modified;
context.SaveChanges();

是的,这似乎很愚蠢,但原因是涉及到该方法之前创建了foo并将其传递给该方法,现在该方法只接收someValue并自己创建foo

简单解决方法,只需将EntityState.Modified更改为EntityState.Added或更改整行为:

context.MyObject.Add(foo);

谢谢您发布这个问题。这也是我的问题,我复制粘贴了一些代码,其中设置了状态为EntityState.Modified。 - clayRay

24

我遇到了同样可怕的错误... :) 然后我意识到,我忘记设置一个

@Html.HiddenFor(model => model.UserProfile.UserId)

用于正在更新的对象的主键! 我往往会忘记这个简单但非常重要的事情!

顺便说一下:HiddenFor 是针对 ASP.NET MVC 的。


3
UserId存储在表单中似乎存在安全漏洞,非常容易受到黑客攻击...应该从HttpContext.Current.User.Identity.Name中后期填充。 - Serj Sagan
@SerjSagan 你说得对...但只要在服务器端进行一些检查以确认UserId和当前UserName,那就没问题了。 - Leniel Maccaferri
2
我的观点是为什么要将它存储在“HiddenFor”中,你还是需要从“HttpContext”中获取它...我根本不会将此属性放在表单中,这将迫使我始终在服务器端填充它... - Serj Sagan
这不是真的:“哇,很多答案,但当我做了一些稍微不同的事情时,我遇到了这个错误,其他人都没有提到。”用户webtrifusion提到了你的情况! - HelloWorld

16

这个问题可能是由以下两种情况之一引起的:

  1. 您尝试更新一个或多个属性为 Concurrency Mode: Fixed 的行,但乐观并发性阻止了数据保存。也就是说,在您接收到服务器数据和保存服务器数据之间,有人更改了行数据。
  2. 您尝试更新或删除一行,但该行不存在。这是另一个例子,即在检索然后保存期间,某人更改了数据(在本例中,是删除了数据),或者您正在尝试更新不是标识的字段(即 StoreGeneratedPattern = Computed),而该行不存在。

1
如果分配给对象的所有属性都与之前相同,则也可能导致此问题。 - Serj Sagan
+1 for 2nd one. 我之前的 StoreGeneratedPattern = None,将其改为 StoreGeneratedPattern = Identity 解决了问题。谢谢。 - tkt986

16

+1. 对我来说是完美而简单的解决方案。我正在将GridView绑定到EntityDataSource,并且没有将其设置为对象的主键。 - Andez
我们知道Kendo UI不支持复合键,但是如果我添加了一个新列将我的键连接在一起,那会发生什么呢? - Branislav

13

我之前也遇到过这个错误,因为主键的部分是一个datetime列,而被插入的记录在该列中使用了DateTime.Now作为值。Entity framework会以毫秒精度插入值,然后搜索刚刚插入的值也使用毫秒精度。但是SqlServer将值舍入至秒精度,因此entity framework无法找到毫秒精度的值。

解决方法是在插入之前从DateTime.Now中截断毫秒。


2
我们遇到了相同的问题,只不过我们是将 DateTime 值插入到 Date 列中。 - adam0101
1
一样的情况。我们有一个数据仓库记录,并且使用时间戳作为键的一部分。数据仓库中的时间戳是SQL DateTime类型,但C#中的时间戳不匹配。我将SQL数据类型更改为DateTime2(7),更新了EF模型,问题得到解决。 - mmcfly
将列更改为Datetime2(7)对我也起作用了。谢谢@mmcfly - Dzejms

10
我遇到了同样的问题,@webtrifusion的答案帮助我找到了解决方法。
我的模型在实体的ID上使用了Bind(Exclude)属性,导致在HttpPost时实体的ID值为零。
namespace OrderUp.Models
{
[Bind(Exclude = "OrderID")]
public class Order
{
    [ScaffoldColumn(false)]
    public int OrderID { get; set; }

    [ScaffoldColumn(false)]
    public System.DateTime OrderDate { get; set; }

    [Required(ErrorMessage = "Name is required")]
    public string Username { get; set; }
    }
}   

类似的问题,出于安全原因,我已经绑定了一些字段。ID不在列表中。我还添加了一个隐藏输入框。可能是MVC生成的某些内容被删除了,或者根本没有这个ID。 感谢您的帮助。 - MusicAndCode

10

在我的情况下,行版本为空。 - wickjon
在我的情况下,我不小心在我的[Bind(Include = properties)]中删除了Id字段。将其添加回去后,它就正常工作了。 - Caverman

9
我也遇到了这个错误。问题是由我试图保存到的表上的触发器引起的。触发器使用了“INSTEAD OF INSERT”,这意味着从未向该表插入任何行,因此出现了错误。幸运的是,在我的情况下,触发器功能不正确,但我想这可能是一个有效的操作,应该在代码中进行处理。希望这能帮助某个人。

2
实体可以通过从触发器返回一个SELECT语句(带有主键列)来欺骗认为行已被添加。 - jahu
2
为了扩展@jahu的评论,我必须从我的触发器中获取新插入项的实际ID以返回,并且列名必须与触发器表的标识列匹配(在我的情况下,实际上是一个视图,因此它没有自己的标识,但我已经欺骗edmx相信它有)。我的触发器正在将数据插入到另一个表中,因此我只需将此最后一行添加到我的触发器中:SELECT SCOPE_IDENTITY() as MyViewId - DannyMeister
还可以参考这个问题:https://dev59.com/UG025IYBdhLWcg3wvIu2 - J. Polfer
在我的情况下,我正在对子集合中的实体执行删除操作,但其中一个子实体上有一个触发器,在删除时导致另一个子实体被删除。这导致错误,因为由于触发器在实体框架尝试删除它之前删除了其中一个子实体本身,所以受影响的行数为 N - 1。 - skeletank

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接