0

我嘗試搜索SO,但是我發現的所有結果似乎都涉及更新已經保存到數據庫的實體的PK。我的情況不同。多個鏈接的1-1關係不按預期同步PK

我有一個數據庫中有1-0..1個關係的3個表。的關係是這樣的:

A <- B <- C 

其中「< - 」代表的關係,並指出主要的結束。即每個B總是有一個相關的A,但一個A可以沒有B.換句話說,A的基數是1,B的是0..1。

每個關係都由從子實體的PK到父實體的PK的FK表示。每個PK是具有客戶端生成值的uniqueidentifierId列。我已經從數據庫中生成了一個EF 4模型,它與相同的基數具有相同的關係。

我試圖將子實體BC添加到現有的A實體。由於設計原因,這兩個新實例是在代碼的和平下創建的,並且A實體與另一個實體中的B實體鏈接。此外,我不希望後者知道存在C

這裏的BC創建代碼的樣子:

public B CreateB() 
{ 
    return new B 
    { 
     Id = Guid.NewGuid(), 
     C = new C(), 
    }; 
} 

而現在的鏈接並保存代碼:

// a is an instance of A that has been loaded from DB 
// and hence has a persistent Id value. 
// b is a just-created instance of B 
// that has a non-persistent Id value and null reference to A. 
void SaveBlahBlahBlah(A a, B b) 
{ 
    // At this point b and c have the same Id value. 
    // It differs from a's Id, but that's expected, they haven't been linked yet. 
    b.A = a; 
    // At this point b receives a's Id value, but c keeps the original one, 
    // therefore the existing b-c link gets broken! 

    using(var ctx = new MyContext()) 
    { 
    ctx.As.Attach(a); // This throws exception saying 
    // I've violated referential integrity. 
    // It doesn't say which relationship is broken, 
    // but I guess it's the B-C one since 
    // the debugger shows them to have different values if PKs 

    ctx.Bs.AddObject(b); 

    ctx.SaveChanges(); 
    } 
} 

我已經試過這既與默認EF的代碼生成器(使用EF的Entity類作爲生成實體的基類)以及自跟蹤實體代碼生成器。結果是一樣的。

因此,代碼崩潰。原因很可能是AB已鏈接後,BC獲得不同的PK值,這對於具有1-1關係的實體是非法的。

我期望的是C自動將它的PK同步到從A實例獲得的值B。這似乎是合理的,因爲我使用對象圖,我有一個現有的B - C關係,這是可以的,並且我希望鏈接BA之後它仍然保持正常。爲什麼會突破?如果BC存在於數據庫中並且我無法更改它們的PK,我會理解它。但情況並非如此,兩個實體都是剛創建的。

因爲EF要求1-1關係的雙方都是PK,所以我不能通過使用與FK的PK列分開來打破密鑰鏈。

我不想手動同步密鑰,因爲事實上有更多的1-1相關的表,這將需要同步代碼出現在很多地方。

我相信我將能夠更新STE生成器的T4模板,以將PK更新降級到1-1關係。但我對T4不太熟悉,不太樂意這樣做。

我有2個問題:

  1. 是我在我的情況下級聯PK更新的預期錯了因爲某些原因? (雖然看起來很奇怪)也就是說,它是一個錯誤還是一個特徵?
  2. 除修改STE模板之外是否還有其他解決問題的方法?也許在EF映射或上下文中有一些神奇的選項?

在此先感謝。

回答

3

問題是,處理從一個引用對象到另一個引用對象的ID分配的服務是上下文。但是在你實際建立關聯的時候,任何對象都不在上下文中。這通常不會成爲問題,因爲當您將B添加到上下文時,關係將會被修復。

不幸的是,你不這樣做。相反,您與A創建了一個額外的關係,但隨後對上下文撒謊並聲稱一切已經修復。更準確地說,您可以撥打EntitySet.Attach,這實際上只適用於已經固定的物體。

在另一方面,這樣的代碼應該只是罰款:

public B CreateB() 
{ 
    return new B 
    { 
     Id = Guid.NewGuid(), 
     C = new C(), 
    }; 
} 

void SaveBlahBlahBlah(A a, B b) 
{  
    using(var ctx = new MyContext()) 
    {  
    ctx.Bs.AddObject(b); 

    ctx.SaveChanges(); 
    } 
} 

注意,所有我所做的是刪除有問題的代碼,其中有無關B和C之間的關係

總之,要小心Attach。當你打電話時,你需要知道你在做什麼。

UPDATE

,處理的A現有的情況下一個版本:

void SaveBlahBlahBlah(A a, B b) 
{  
    Debug.Assert(a.B != b); 

    using(var ctx = new MyContext()) 
    {  
    ctx.As.Attach(a); 

    a.B = b; // it's crucial that this link is set after attaching a to context! 

    ctx.Bs.AddObject(b); 

    ctx.SaveChanges(); 
    } 
} 
+1

+1解釋得很清楚! – 2012-03-27 19:36:24

+0

謝謝。我應該注意到我使用了'Attach',因爲沒有它,EF試圖插入'A',由於'A'已經存在於數據庫中,導致錯誤。從那時起,我改變了模型和風格(當時我使用了EFCF)。所以也許你的建議會奏效,我會試試看。 – 2012-03-28 01:45:18

+0

您可以使用'Attach',但您需要在*與B建立關係之前進行此操作。* – 2012-03-28 02:25:55