2010-06-30 145 views
3

下面试图更新数据库中的一行列出的代码中DuplicateKeyException,但不是抛出一个异常:的LINQ to SQL - 更新

System.Data.Linq.DuplicateKeyException: Cannot add an entity with a key that is already in use

大多数例子我见过的查询数据库获取的实例一个实体,修改一些实例的属性,然后更新它。在这里我从不同的来源获取对象完全(它正在从一个XML文件解析)和查询,看是否已经存在该数据的行。如果有,我正在设置主键并尝试运行更新。什么是正确的方法来做到这一点?

下面的代码的下调版本:

Customer customer = new Customer(); // Customer has a database generated 
            // identity column called CustomerId 

// populate customer object 
customer.Name = "Mr. X"; 
customer.Email = "[email protected]"; 
// 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

显然这不是一个新问题。下面是一些讨论这个问题的帖子采样:

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和表工作。我的修改代码如下所示:

Customer customer = new Customer(); // Customer has a database generated 
            // identity column called CustomerId 

// populate customer object 
customer.Name = "Mr. X"; 
customer.Email = "[email protected]"; 
// 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.

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

有趣。我猜这就是我所建议的(但在代码中)。你能否更多地解释一下LINQ to SQL的新手如何工作? – JasCav 2010-06-30 15:43:02

+0

我试过了。它给了我同样的错误。 – MCS 2010-06-30 15:43:10

+0

在我看来,customerTable.Attach(客户,现有)也应该有效。但是,如果我没有设置customer.CustomerId,我得到的错误:System.InvalidOperationException:类型为'Customer'的对象的成员'CustomerId'的值发生了变化。定义对象身份的成员不能更改。当我设置Customer.CustomerId时,我得到了DuplicateKeyException!去搞清楚。 – MCS 2010-06-30 17:26:25

1

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

相反,你要更新现有客户并提交回数据库。

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填补客户,然后做你的插入/更新。

+0

您的意思是将现有的所有属性设置为与客户中的属性相匹配?类似于existing.name = customer.name和existing.email = customer.email等。似乎应该有一个更简单的方法来做到这一点。 – MCS 2010-06-30 15:47:04

+0

是的,但是,Attach会为你做这件事。然而看看我的帖子进行编辑。 – msarchet 2010-06-30 16:09:55

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