LINQ to SQL - 更新期间的DuplicateKeyException

时间:2010-06-30 15:32:48

标签: c# linq-to-sql

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

  

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
// ...   

5 个答案:

答案 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

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<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.