2010-06-30 185 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(); 
      } 
    }