LINQ to SQL – 更新期间的DuplicateKeyException

下面列出的代码尝试更新数据库中的行,但会引发exception:

System.Data.Linq.DuplicateKeyException:无法添加具有已在使用的键的实体

我见过的大多数示例都是查询数据库以获取实体的实例,修改某些实例的属性,然后更新它。 在这里,我完全从不同的源获取对象(它是从XML文件中解析的)并查询是否已经有一行用于此数据。 如果有,我正在设置主键并尝试运行更新。 这样做的正确方法是什么?

这是代码的修剪版本:

Customer customer = new Customer(); // Customer has a database generated // identity column called CustomerId // populate customer object customer.Name = "Mr. X"; customer.Email = "x@company.com"; // etc. // is customer already in database? // identify customer by email var results = ctx.Where(c => c.Email == customer.Email); // ctx is a DataContext if (results.Any()) { Customer existing = results.Single(); // set primary key to match existing one customer.CustomerId = existing.CustomerId; // update database customerTable.Attach(customer); // customerTable is a Table ctx.SubmitChanges(); } // otherwise do insert // ... 

我是LINQ to SQL的新手,所以如果比我聪明的人认为这是错的,请纠正我。 但是,我相信你的问题是当你进入if语句时,你从结果中获取实体(通过results.Single())并且你将值设置为NEW客户对象。 当您尝试将客户对象提交到数据库时,主键已存在,因此您收到错误。

相反,您希望更新现有客户并将其提交回数据库。

进行此更改:

 customerTable.Attach(customer, existing); 

^我不确定为什么以上不起作用。 第二个参数是实体的“原始状态”,也许是因为它是对不同实例的不同引用,L2S认为它需要插入一个全新的对象。

我认为做以下事情会更好:

 var customer = ctx.Where(...).SingleOrDefault(); if (customer == null) { customer = new Customer() { Name = name, Email = email }; customerTable.InsertOnSubmit(customer); } else { customer.Name = name; customer.Email = email; } ctx.SubmitChanges(); 

在网络的某个地方,我找到了这个解决方案:

 static void CopyProperties(ref T Target, T Source) { foreach (PropertyInfo PI in Target.GetType().GetProperties()) { if (PI.CanWrite && PI.CanRead) { PI.SetValue(Target, PI.GetValue(Source, null), null); } } } 

….

 static void Save(Test_TableData ChangedData) { using (DataClasses1DataContext D = new DataClasses1DataContext()) { Test_TableData UpdateTarget = D.Test_TableDatas.SingleOrDefault(i => i.ID == ChangedData.ID); if (UpdateTarget != null) { CopyProperties(ref UpdateTarget, ChangedData); } else { D.Test_TableDatas.InsertOnSubmit(ChangedData); } D.SubmitChanges(); } } 

我这样做的方法如下

我会像你一样拉出已存在的客户,然后用你正在使用的匹配ID更新项目,并使用从xml中提取的值。 然后当你调用datacontext.SubmitChanges()方法时,它会为你做更新。

或者你可以使用Attach方法,在你的情况下你的代码看起来像

customerTable.Attach(customer, existing);

完全为此案例创建了Attach

编辑

你为什么不改变你正在做的事情的顺序,而不是建立一个新的客户,填充该客户,做这样的事情

 var results = ctx.Where(c => c.Email == customer.Email); Customer customer = (results.Any ? results.Single : new Customer) 

然后查询您的xml以填充客户,然后进行插入/更新。

显然这不是一个新问题。 以下是讨论此问题的一些post的示例:

http://www.west-wind.com/weblog/posts/134095.aspx

http://www.codeproject.com/KB/linq/linq-to-sql-detach.aspx

http://social.msdn.microsoft.com/forums/en-US/linqprojectgeneral/thread/3848c02c-464e-40ff-87b6-813bff7b1263/

在进行更新之前,我通过创建新的DataContext和Table来实现它。 我修改过的代码如下所示:

 Customer customer = new Customer(); // Customer has a database generated // identity column called CustomerId // populate customer object customer.Name = "Mr. X"; customer.Email = "x@company.com"; // etc. // is customer already in database? // identify customer by email var results = ctx.Where(c => c.Email == customer.Email); // ctx is a DataContext if (results.Any()) { Customer existing = results.Single(); // set primary key to match existing one customer.CustomerId = existing.CustomerId; // **** CODE CHANGES HERE **** // create new DataContext and table to avoid DuplicateKeyException errors var ctx = new DataContext(customerTable.Context.Connection.ConnectionString); customerTable = ctx.GetTable(); // update database customerTable.Attach(customer); // customerTable is a Table // **** ANOTHER CODE CHANGE **** // without this line the data won't be updated with the new values ctx.Refresh(RefreshMode.KeepCurrentValues, customer); ctx.SubmitChanges(); } // otherwise do insert // ... 

我理解这一点的方式是DataContext只能包含每个唯一实体的一个实例。 尝试使用相同的主键附加新实体会导致错误,因为现在将存在同一实体的两个实例。 新的DataContext不知道现有实体,因此附加新实体没有问题。


更新:看起来这个问题已经得到了回答 。


更新:不要按原样使用我的示例代码。 这给我带来了其他问题。