我在一个项目中使用Entity Framework 4.3.1,使用Code First和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 操作对象图。例如,如果我有一个封装 A 实例的 B 实例,并调用 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无法继续,因为对象的键值与ObjectStateManager中的另一个对象冲突。在调用AcceptChanges之前,请确保键值是唯一的。”我认为这是因为对myB0调用add方法将A的实例标记为已添加,这与已跟踪的A实例冲突。
理想情况下,我可以像调用
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);
尝试 #1: 我尝试将myA的状态设置为Unchanged,然后使用Attach(myA),但出现ObjectStateManager中的键冲突。虽然解决了添加myA的问题,但仍然在ObjectStateManager中引起了键冲突。
尝试 #2: 我尝试像上面那样操作,但将状态设置为Detached而不是Unchanged。这适用于保存,但它坚持要将myB0.A = null,这对我的代码有其他不利影响。
尝试 #3: 我在整个DbContext周围使用TransactionScope。但是,即使在每个Attach()和Add()之间调用SaveChanges(),更改跟踪器也不会刷新其已跟踪的条目,因此我与尝试#1中相同的问题。
尝试 #4: 我继续使用TransactionScope,但使用存储库/ DAO模式,并在内部创建一个新的DbContext并为我执行的每个不同操作调用SaveChanges()。在这种情况下,我收到错误“存储更新、插入或删除语句影响了意外数量的行。”当使用SQL Profiler时,我发现在第二个操作(第一个Add())上调用SaveChanges()时,它实际上将UPDATE SQL从第一个操作再次发送到数据库,但不更改任何行。这让我觉得Entity Framework存在缺陷。
尝试 #5: 我决定仅使用DbTransaction而不是TransactionScope。我仍然创建多个上下文,但在每次创建新上下文时将预构建的EntityConnection传递给它(通过缓存并手动打开由第一个上下文构建的EntityConnection)。但是,当我这样做时,第二个上下文会运行我定义的初始化程序,即使它已经在应用程序启动时运行过了。在dev环境中,我有一些测试数据进行种植,它实际上超时等待数据库锁定我第一个Attach()修改的表(但由于事务仍处于打开状态而仍被锁定)。
救命啊!!我尝试了我能想到的所有方法,除了完全重构我的应用程序以不使用导航属性或使用手动构建的DAO执行INSERT、UPDATE和DELETE语句外,我束手无策。似乎必须有一种方法才能获得Entity Framework用于O / R映射的好处,但仍然手动控制事务内部的操作!