AddOrUpdate导致错误的外键更新

时间:2015-08-29 14:32:12

标签: c# entity-framework

我遇到与Stack Overflow报告的错误相同的错误:EntityFramework's AddOrUpdate leads to incorrect foreign key update

保持简短,在我调用AddOrUpdate插入新记录后,如果我再调用AddOrUpdate来更新同一条记录,则会抛出异常。

示例:

State.cs
int Id;
string Name;

City.cs
int Id;
int StateId;
string Name;
int Location;

// Crud
var state = new State { Name = "NY" };
Context.States.AddOrUpdate(p => p.Name, state);
Context.SaveChanges();

// Adds with location equals to 1
var city = new City { Name = "NYC", Location = "1", State = state };
Context.Cities.AddOrUpdate(p => p.Name, city);
Context.SaveChanges();

// Updating the location to 2, leads to EF trying to set StateId to 0
var city = new City { Name = "NYC", Location = "2", State = state };
Context.Cities.AddOrUpdate(p => p.Name, city);
Context.SaveChanges();

抛出异常,说外键StateId = 0

堆栈追踪:

"The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_dbo.City_dbo.State_StateId". The conflict occurred in database "C:\X\APP_DATA\LOCAL.MDF", table "dbo.State", column 'Id'.
The statement has been terminated."

System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.<NonQuery>b__0(DbCommand t, DbCommandInterceptionContext`1 c)
System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext,TResult](TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)
System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.NonQuery(DbCommand command, DbCommandInterceptionContext interceptionContext)
System.Data.Entity.Internal.InterceptableDbCommand.ExecuteNonQuery()
System.Data.Entity.Core.Mapping.Update.Internal.DynamicUpdateCommand.Execute(Dictionary`2 identifierValues, List`1 generatedValues)
System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()

感谢

1 个答案:

答案 0 :(得分:2)

AddOrUpdate仅适用于简单的播种操作。在某些情况下它是know to be buggy,这也应该是一个bug。这是出乎意料的行为。

它可能与如何将属性值从提交的实例(您的city变量)复制到从数据库获取的实例有关。显然,只考虑原始属性。

发表声明后......

var city = new City { Name = "NYC", Location = "2", State = state };

... cityStateId = 0.这是分配给数据库实例的值(与city不同的实例)。

一旦您意识到这一点,就可以通过设置StateId而不是State来解决此问题。

但我认为你不应该在常规业务逻辑中使用AddOrUpdate。首先,因为它是错误的(或者不完整的最好)并且我不知道还有什么存储。但也因为这是一个非常沉重的方法。它从数据库中获取一个完整的实体,带有跟踪和所有实体。

但通常情况下,如果您想要更新实体,很可能您之前已经提取过它,然后在某个过程中将其更新,现在又想再次提交。即使在N层应用程序(使用序列化/反序列化等)中,您可能仍然拥有对象的Id,您可以通过它确定是应该插入还是更新它。因此,在大多数情况下,您无需从数据库中重新获取实体,而AddOrUpdate将始终执行该操作。