【问题标题】:LINQ to SQL - DuplicateKeyException during updateLINQ to SQL - 更新期间出现 DuplicateKeyException
【发布时间】:2010-06-30 15:32:48
【问题描述】:

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

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

【问题讨论】:

    标签: c# linq-to-sql


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

      进行此更改:

      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();
      

      【讨论】:

      • 有趣。我猜这正在做我建议的事情(但在代码中)。您能否向 LINQ to SQL 的新手解释一下它是如何工作的?
      • 在我看来,customerTable.Attach(customer, existing) 也应该可以工作。但是,如果我不设置 customer.CustomerId,我会收到错误消息:System.InvalidOperationException:'Customer' 类型的对象的成员'CustomerId' 的值已更改。不能更改定义对象身份的成员。当我设置 Customer.CustomerId 时,我得到 DuplicateKeyException!去图吧。
      【解决方案3】:

      我在网上某处找到了这个解决方案:

      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();
                  }
          }
      

      【讨论】:

        【解决方案4】:

        我这样做的方式如下

        我会像您一样拉出已经存在的客户,然后使用您正在使用的匹配 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 以填充客户,然后执行您的插入/更新。

        【讨论】:

        • 您的意思是设置现有的所有属性以匹配客户中的属性吗?像existing.name = customer.name 和existing.email = customer.email 等等等等。似乎应该有更简单的方法来做到这一点。
        • 是的,但是,Attach 会为您执行此操作。但是请查看我的帖子进行编辑。
        【解决方案5】:

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

        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.

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-05-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-02
          相关资源
          最近更新 更多