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