下面列出的代码尝试更新数据库中的行,但会引发异常:
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<Customer>
ctx.SubmitChanges();
}
// otherwise do insert
// ...
答案 0 :(得分:1)
进行此更改:
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();
答案 1 :(得分:1)
我是LINQ to SQL的新手,所以如果比我更聪明的人看到这是错误的,请纠正我。但是,我相信你的问题是当你进入if语句时,你从结果中获取实体(通过results.Single())并且你将值设置为NEW客户对象。当您尝试将客户对象提交到数据库时,主键已存在,因此您收到错误。
相反,您希望更新现有客户并将其提交回数据库。
答案 2 :(得分:1)
在网络的某个地方,我找到了这个解决方案:
static void CopyProperties<T>(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<Test_TableData>(ref UpdateTarget, ChangedData);
}
else
{
D.Test_TableDatas.InsertOnSubmit(ChangedData);
}
D.SubmitChanges();
}
}
答案 3 :(得分:0)
我这样做的方式如下
我会像你一样拉出已存在的客户,然后用你正在使用的匹配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以填充客户,然后进行插入/更新。
答案 4 :(得分:0)
显然这不是一个新问题。以下是讨论此问题的一些帖子的示例:
http://www.west-wind.com/weblog/posts/134095.aspx
http://www.codeproject.com/KB/linq/linq-to-sql-detach.aspx
我通过在进行更新之前创建一个新的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<Customer>();
// update database
customerTable.Attach(customer); // customerTable is a Table<Customer>
// **** 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不知道现有实体,因此附加新实体没有问题。
更新:看起来像has already been answered这个问题。
更新:不要按原样使用我的示例代码。 It caused me other problems.