我在项目中使用实体框架 4.3.1,首先使用代码和 DbContext API。我的应用程序是一个 n 层应用程序,其中断开连接的对象可能来自客户端。我正在使用 SQL Server 2008 R2,但很快将迁移到 SQL Azure。我遇到了一个我似乎无法解决的问题。
想象一下我有几节课:
class A {
// Random stuff here
}
class B {
// Random stuff here
public A MyA { get; set; }
}
class C {
// Random stuff here
public A MyA { get; set; }
}
默认情况下,EF 对对象图进行操作。例如,如果我有一个 B 的实例,它封装了 A 的一个实例并且我调用myDbSet.Add(myB);
,它也会将 A 的实例标记为正在添加(假设它尚未被跟踪)。
我的应用程序中有一个场景,我需要明确哪些对象被持久化到数据库中,而不是让它跟踪整个对象图。操作顺序如下:
A myA = new A(); // Represents something already in DB that doesn't need to be udpated.
C myC = new C() { // Represents something already in DB that DOES need to be updated.
A = myA;
}
B myB0 = new B() { // Not yet in DB.
A = myA;
}
B myB1 = new B() { // Not yet in DB.
A = myA;
}
myDbSetC.Attach(myC);
context.Entry(myC).State = Modified;
myDbSetB.Add(myB0); // Tries to track myA with a state of Added
myDbSetB.Add(myB1);
context.SaveChanges();
此时我收到一条错误消息,说AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.
我相信会发生这种情况,因为在 myB0 上调用 add 会将 A 的实例标记为已添加,这与已跟踪的 A 实例冲突。
理想情况下,我可以做类似 call的事情myDbSet.AddOnly(myB)
,但显然我们没有那个选项。
我尝试了几种解决方法:
尝试#1: 首先,我尝试创建一个辅助方法来防止 myA 被第二次添加。
private void MarkGraphAsUnchanged<TEntity>(TEntity entity) where TEntity : class {
DbEntityEntry entryForThis = this.context.Entry<TEntity>(entity);
IEnumerable<DbEntityEntry> entriesItWantsToChange = this.context.ChangeTracker.Entries().Distinct();
foreach (DbEntityEntry entry in entriesItWantsToChange) {
if (!entryForThis.Equals(entry)) {
entry.State = System.Data.EntityState.Unchanged;
}
}
}
...
myDbSetB.Add(myB0);
MarkGraphAsUnchanged(myB0);
虽然这解决了它尝试添加 myA 的问题,但它仍然会导致 ObjectStateManager 中的键违规。
尝试 #2:
我尝试执行与上述相同的操作,但将状态设置为 Detached 而不是 Unchanged。这适用于保存,但它坚持设置myB0.A = null
,这对我的代码有其他不利影响。
尝试 #3:
我在整个 DbContext 周围使用了 TransactionScope。但是,即使SaveChanges()
在每个Attach()
and之间调用Add()
,更改跟踪器也不会刷新其跟踪的条目,所以我遇到了与尝试 #1 相同的问题。
尝试#4:
我继续使用 TransactionScope,除了我使用存储库/DAO 模式并在内部创建一个新的 DbContext 并调用SaveChanges()
我所做的每个不同的操作。在这种情况下,我收到错误消息“存储更新、插入或删除语句影响了意外的行数。” 使用 SQL Profiler 时,我发现在调用我SaveChanges()
执行的第二个操作(第一个Add()
)时,它实际上第二次将UPDATE
SQL 从第一个操作发送到数据库——但不会更改任何行。对我来说,这感觉就像实体框架中的一个错误。
尝试 #5:
我决定只使用 DbTransaction,而不是使用 TransactionScope。我仍然创建多个上下文,但是在创建每个新上下文时将一个预构建的 EntityConnection 传递给它(通过缓存和手动打开由第一个上下文构建的 EntityConnection)。但是,当我这样做时,第二个上下文会运行我定义的初始化程序,即使它在应用程序首次启动时已经运行。在开发环境中,我有一些测试数据的种子,它实际上超时等待我第一次Attach()
修改的表上的数据库锁定(但由于事务仍然打开而仍然被锁定)。
帮助!!我已经尝试了所有我能想到的东西,但没有完全重构我的应用程序以不使用导航属性或使用手动构建的 DAO 来执行 INSERT、UPDATE 和 DELETE 语句,我不知所措。似乎必须有一种方法可以让实体框架为 O/R 映射带来好处,但仍然需要手动控制事务中的操作!