我在使用“断开连接”上下文时遇到 EF6 问题。我首先使用数据库,并以这种方式建模了多对多关系(显示的关系后面有一个联结表,带有两个 FK,它们一起构成了联结表的复合 PK。其中没有其他列连接表):
由于我想以断开连接的方式使用 EF(短暂的上下文,为 Web API 做好准备),我还实现了 Julie Lerman 和 Rowan Millers 关于 DbContext 的书中的“绘制状态”方法。具体来说,他们在本书的第 4 章“记录原始值”(第 102 页及后续内容)中描述了方法。这是我的 ApplyChanges 方法:
public static void ApplyChangesInGraph<TEntity>(TEntity root) where TEntity : class, IObjectWithState
{
using (var context = new NovaEntities2())
{
context.Set<TEntity>().Add(root);
foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
if (stateInfo.State == State.Unchanged)
{
ApplyPropertyChanges(entry.OriginalValues, stateInfo.OriginalValues);
}
}
context.SaveChanges();
}
}
完成此操作后,我在以下测试中遇到异常:
[Fact]
public void ShouldNotResultInAnInsertOfPlaceOfEmployment()
{
ResetDbToKnownState();
Employee employee;
using (var context = new NovaEntities2())
{
employee = context.Employees
//.Include(e => e.PlacesOfEmployment) // If enabled, an exception is thrown.
.First();
}
employee.Name = "A new name";
NovaEntities2.ApplyChangesInGraph(employee);
}
如果我启用上面的.Include,则会出现以下异常:
System.Data.Entity.Infrastruct.DbUpdateException发生错误 同时保存不公开外键属性的实体 他们的关系。 EntityEntries 属性将返回 null 因为无法将单个实体识别为该信息的来源 例外。保存时处理异常可以更容易 公开实体类型中的外键属性。请参阅 有关详细信息,请参阅 InnerException。
InnerException:System.Data.SqlClient.SqlExceptionViolation of PRIMARY 键约束“PK_EmployeePlaceOfEmployment”。无法插入重复项 键入对象“dbo.EmployeePlaceOfEmployment”。重复的键值 是 (140828, 14)。 IE。通过上面添加的 .include,EF 发出 更新员工(很好)并插入已经存在的员工 对于上面的简单情况,PlaceOfEmployment(不太好),我 仅尝试更新员工的姓名。
我一生都无法弄清楚为什么 INSERT 发生在这里,违反了主键。我已尝试单步执行ApplyChanges 方法中的foreach,并验证所有实体状态均已正确设置。事实上,在第一次迭代中,Employee 实体开始时处于“已添加”状态,最终处于“已修改”状态。接下来,正在处理急切加载的 PlaceOfEmployment 实体,它开始时处于“已添加”状态,最终处于“未更改”状态。然而,仍然生成 INSERT,导致异常。
来自 SQL 探查器:
我不知道这种“绘制状态”方法,但你的示例不起作用我并不感到惊讶。
使用
context.Set<TEntity>().Add(root)
会发生三件事:
employee
) 处于 Added
状态employee.PlacesOfEmployment
) 都处于 Added
状态Added
状态当您迭代
context.ChangeTracker.Entries
时,您只会从上下文中获取 entities,而不是 relationship items (它们是存储在上下文中的单独工件)。在此循环中,您将根和子级的状态分别重置为 Modified
或 Unchanged
。但这只是实体状态的改变。 关系状态仍然是Added
,并且这些状态被转换为“关系表”中的INSERT
。这就是为什么您会收到重复键异常,因为链接根和子项的记录已经存在。
虽然底层
ObjectContext
还允许访问除实体状态条目之外的关系条目,但我怀疑这会有帮助,因为 ApplyChangesInGraph
方法如何识别传递到该方法的图形已添加或删除或未更改的关系,如果每个实体只有一个 State
属性?
现在,如果您使用
context.Set<TEntity>().Attach(root);
而不是 ...Add(root)...
,您可能可以使您的测试方法起作用,因为这会将所有实体和关系置于 Unchanged
状态(或者 - 我相信 - 它不会在中存储任何关系条目上下文)。因此,不应发生对链接表的插入(也不应从该表进行删除)。但是,我担心,使用 ...Attach
时,如果您有一个具有真正新(或已删除)关系的对象图,并且您实际上期望插入(或删除),则
ApplyChangesInGraph
方法将不再正确工作对于链接表。
老实说,我不知道如何实现该通用方法,以便它一般适用于所有可能的更改场景。在我看来,对象图(每个实体有一个
State
属性)并没有包含足够的信息来描述图断开连接时可能发生的所有关系更改。 (我对 Julie Lerman 和 Rowan Miller 提议如何实施它感兴趣。)