首先使用Entity Framework代码保存单个对象

Jam*_*ier 5 entity-framework entity-framework-4.1 entity-framework-4.3

我在项目中使用Entity Framework 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; }
}
Run Code Online (Sandbox Code Playgroud)

默认情况下,EF在对象图上运行.例如,如果我有一个B的实例封装了A和I的实例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();
Run Code Online (Sandbox Code Playgroud)

在这一点上,我得到一个错误,说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的实例标记为Added,这与已经被跟踪的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);
Run Code Online (Sandbox Code Playgroud)

虽然这解决了它试图添加myA的问题,但仍然会导致ObjectStateManager中的密钥冲突.

尝试#2: 我尝试过如上所述,但是将状态设置为Detached而不是Unchanged.这适用于保存,但它坚持设置myB0.A = null,这在我的代码中有其他不利影响.

尝试#3: 我在整个DbContext周围使用了TransactionScope.但是,即使SaveChanges()在每个Attach()和之间进行调用时Add(),更改跟踪器也不会刷新其跟踪的条目,因此我遇到与尝试#1相同的问题.

尝试#4: 我继续使用TransactionScope,除了我使用了存储库/ DAO模式并在内部创建了一个新的DbContext并调用SaveChanges()我做的每个不同的操作.在这种情况下,我收到错误"存储更新,插入或删除语句影响了意外的行数." 使用SQL事件探查器时,我发现在调用我做SaveChanges()第二个操作(第一个Add())时,它实际上是第二次UPDATE从第一个操作将SQL 发送到数据库- 但是不会更改任何行.这对我来说就像实体框架中的一个错误.

尝试#5: 我决定只使用DbTransaction而不是使用TransactionScope.我仍然创建多个上下文,但是在创建时将预先构建的EntityConnection传递给每个新上下文(通过缓存并手动打开由第一个上下文构建的EntityConnection).但是,当我这样做时,第二个上下文运行我已定义的初始化程序,即使它已经在应用程序首次启动时运行.在开发环境中,我有一些测试数据,它实际上超时了我第一次Attach()修改的表上的数据库锁(但由于事务仍处于打开状态,仍然被锁定).

救命!!我已经尝试了我能想到的一切,并且没有完全重构我的应用程序以不使用导航属性或使用手动构造的DAO来执行INSERT,UPDATE和DELETE语句,我很茫然.似乎必须有一种方法可以获得实体框架的O/R映射的好处,但仍然可以手动控制事务中的操作!

Lad*_*nka 2

一定还有其他东西您没有显示,因为您附加和添加实体的方式没有问题。以下代码将myAmyCmyB0和附加myB1到上下文作为未更改,并将 的状态设置为myC已修改。

myDbSetC.Attach(myC);
context.Entry(myC).State = Modified;
Run Code Online (Sandbox Code Playgroud)

以下代码将正确检测到所有实体已附加,并且不会抛出异常(如在 ObjectContext API 中那样)或再次插入所有实体(如您所期望的),它只会更改myB0myB1添加状态:

myDbSetB.Add(myB0);
myDbSetB.Add(myB1);
Run Code Online (Sandbox Code Playgroud)

如果您的myAmyC已使用现有实体的键正确初始化,则整个代码将正确执行并保存,除了单个问题:

C myC = new C() { 
    A = myA;
}
Run Code Online (Sandbox Code Playgroud)

这看起来像独立关联,独立关联有自己的状态,但DbContext API 中不提供用于设置其状态的 API 。如果这是您想要保存的新关系,它将不会被保存,因为它仍然被跟踪为未更改。您必须使用外键关联,或者必须将上下文转换为ObjectContext

ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
Run Code Online (Sandbox Code Playgroud)

并用于ObjectStateManager改变关系的状态