我尝试搜索SO,但我发现的所有结果似乎都涉及更新已经持久保存到数据库的实体的PK。我的情况不同。
我在数据库中有3个表,有1-0..1的关系。关系看起来像这样:
A <- B <- C
其中'&lt; - '表示关系并指向主要结尾。即每个B总是有一个相关的A,但A可以没有B.换句话说,A的基数是1而B的是0..1。
每个关系由FK表示,FK从子实体的PK到父实体的PK。每个PK都是uniqueidentifier
Id
列,具有客户端生成的值。我从数据库中生成了一个EF 4模型,该模型与基数具有相同的关系。
我正在尝试将儿童访问者B
和C
添加到现有的A
实体中。出于设计原因,在一个代码的代码中创建了几个新实例,并且A
实体链接到另一个实体中的B
实体。此外,我不希望后者知道C
存在。
以下是B
和C
创建代码的显示方式:
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
类作为生成实体的基类)和自跟踪实体代码生成器。结果是一样的。
所以,代码崩溃了。原因可能是在A
和B
被链接后,B
和C
会获得不同的PK值,这对于具有1-1关系的实体来说是非法的。< / p>
我所期望的是C自动将其PK同步到B
实例获得的值A
。这似乎是合理的,因为我使用对象图,我有一个现有的B
- C
关系,这是正常的,我希望在将B
与A
关联后保持正常。它为什么会破裂?如果DB中存在B
或C
并且我无法更改其PK,我会理解。但实际情况并非如此,这两个实体都是刚刚创建的。
我不能通过为FK使用单独的PKs列来破坏密钥链,因为EF要求1-1关系的两边都是PK。
我不想手动同步密钥,因为实际上有更多1-1相关的表,这需要同步代码出现在很多地方。
我相信我将能够更新STE生成器的T4模板,以便在1-1关系中级联PK更新。但我对T4并不太熟悉,也不太乐意这样做。
我有两个问题:
提前致谢。
答案 0 :(得分: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
。你打电话时需要知道自己在做什么。
<强>更新强>
处理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();
}
}