一个具有多个dbcontexts的事务

我在unit testing中使用事务来回滚更改。 unit testing使用dbcontext,我正在测试的服务使用自己的。 它们都包装在一个事务中,一个dbcontext在另一个事务的块中。 问题是,当内部dbcontext保存他的更改时,外部dbcontext不可见(我不认为这是因为另一个dbcontext可能已经加载了对象)。 这是一个例子:

[TestMethod] public void EditDepartmentTest() { using (TransactionScope transaction = new TransactionScope()) { using (MyDbContext db = new MyDbContext()) { //Arrange int departmentId = (from d in db.Departments where d.Name == "Dep1" select d.Id).Single(); string newName = "newName", newCode = "newCode"; //Act IDepartmentService service = new DepartmentService(); service.EditDepartment(departmentId, newName, newCode); //Assert Department department = db.Departments.Find(departmentId); Assert.AreEqual(newName, department.Name,"Unexpected department name!"); //Exception is thrown because department.Name is "Dep1" instead of "newName" Assert.AreEqual(newCode, department.Code, "Unexpected department code!"); } } } 

服务:

 public class DepartmentService : IDepartmentService { public void EditDepartment(int DepartmentId, string Name, string Code) { using (MyDbContext db = new MyDbContext ()) { Department department = db.Departments.Find(DepartmentId); department.Name = Name; department.Code = Code; db.SaveChanges(); } } } 

但是,如果我在调用服务之前关闭外部dbcontext并为断言打开一个新的dbcontext,一切正常:

 [TestMethod] public void EditDepartmentTest() { using (TransactionScope transaction = new TransactionScope()) { int departmentId=0; string newName = "newName", newCode = "newCode"; using (MyDbContext db = new MyDbContext()) { //Arrange departmentId = (from d in db.Departments where d.Name == "Dep1" select d.Id).Single(); } //Act IDepartmentService service = new DepartmentService(); service.EditDepartment(departmentId, newName, newCode); using (MyDbContext db = new MyDbContext()) { //Assert Department department = db.Departments.Find(departmentId); Assert.AreEqual(newName, department.Name,"Unexpected department name!"); Assert.AreEqual(newCode, department.Code, "Unexpected department code!"); } } } 

所以基本上我有一个解决这个问题的方法(在编写这个问题时想到它)但我仍然想知道为什么在嵌套dbcontexts时无法访问事务中的未提交数据。 可能是因为使用(dbcontext)就像一个事务本身? 如果是这样,我仍然不明白这个问题,因为我在内部dbcontext上调用.SaveChanges()。

在第一个场景中,您正在嵌套DbContexts 。 将为每个数据库打开与数据库的连接。 当您在using块中调用服务方法时,在TransactionScope打开一个新连接,而另一个已经打开。 这会导致您的事务被提升为分布式事务 ,并且部分提交的数据(服务中的DbContext.SaveChanges调用的结果)无法从外部连接获得。 另请注意,分布式事务要慢得多,因此会产生降低性能的副作用。

在第二种方案中,当您打开和关闭三个连接时,在您的事务中只有一个连接同时打开。 由于这些连接共享相同的连接字符串 ,因此事务不会自动提升为分布式连接,因此事务中的每个后续连接都可以访问先前连接所执行的更改。

您可以尝试将Enlist=false参数添加到连接字符串中。 这将禁用分布式事务中的自动登记,从而导致在第一个场景中引发exception。 如果您使用SQL Server 2008及更高版本,第二种方案将保持完美运行,因为事务不会得到提升。 ( 在此方案中,SQL Server的早期版本仍将提升事务。 )

你也可以找到一个非常相似的问题的有用的答案 。

过于频繁地使用新环境是一种反模式。 创建一个上下文并传递它。 使用dependency injection框架进行传递非常容易。

但是,如果我在调用服务之前关闭外部dbcontext并为断言打开一个新的dbcontext,一切正常

不,这是巧合,因为第二个上下文重用了连接池中第一个的连接。 这不能得到保证,并且会在负载下中断。

避免分布式事务的唯一方法是使用一个保持打开的连接。

但是,您可以让多个上下文共享相同的连接。 使用手动创建的连接进行实例化以执行此操作。

这有效:

public class Test1 {public int Id {get; 组; public string Name {get; 组; }}

 public class Test2 { public int Id { get; set; } public string Name { get; set; } } public class DC1 : DbContext { public DbSet Test1 { get; set; } public DC1(SqlConnection conn) : base(conn, contextOwnsConnection: false) { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.HasDefaultSchema("dc1"); modelBuilder.Entity().ToTable("Test1"); } } public class DC2 : DbContext { public DbSet Test2 { get; set; } public DC2(SqlConnection conn) : base(conn, contextOwnsConnection: false) { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.HasDefaultSchema("dc2"); modelBuilder.Entity().ToTable("Test2"); } } 

 using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["EntityConnectionString"].ConnectionString)) { conn.Open(); using (var tr = conn.BeginTransaction()) { try { using (var dc1 = new DC1(conn)) { dc1.Database.UseTransaction(tr); var t = dc1.Test1.ToList(); dc1.Test1.Add(new Test1 { Name = "77777", }); dc1.SaveChanges(); } //throw new Exception(); using (var dc2 = new DC2(conn)) { dc2.Database.UseTransaction(tr); var t = dc2.Test2.ToList(); dc2.Test2.Add(new Test2 { Name = "777777", }); dc2.SaveChanges(); } tr.Commit(); } catch { tr.Rollback(); //throw; } App.Current.Shutdown(); } } 

我想最好在事务外部获取,因此不会发生锁定,但我不确定 – 需要自己调查一下

更新:上面的代码使用代码优先方法下面的代码是数据库优先

 public MetadataWorkspace GetWorkspace(Assembly assembly) { MetadataWorkspace result = null; //if (!mCache.TryGetValue(assembly, out result) || result == null) { result = new MetadataWorkspace( new string[] { "res://*/" }, new Assembly[] { assembly }); //mCache.TryAdd(assembly, result); } return result; } 

 using(var conn = new SqlConnection("...")) { conn.Open(); using(var tr = conn.BeginTransaction()) { using(var entityConnection1 = new EntityConnection( GetWorkspace(typeof(DbContext1).Assembly), conn)) { using(var context1 = new ObjectContext(entityConnection1)) { using(var dbc1 = new DbContext1(context1, false)) { using(var entityConnection2 = new EntityConnection( GetWorkspace(typeof(DbContext2).Assembly), conn)) { using(var context2 = new ObjectContext(entityConnection2)) { using(var dbc2 = new DbContext2(context2, false)) { try { dbc1.UseTransaction(tr); // fetch and modify data dbc1.SaveChanges(); dbc2.UseTransaction(tr); // fetch and modify data dbc2.SaveChanges(); tr.Commit(); } catch { tr.Rollback(); } } } } } } } } } 

在您的app中使用许多DbContexts时非常有用。 例如,如果你有数千个表 – 我刚刚创建了所谓的“模块”,每个模块大约有100个表。 虽然我需要在单个事务中进行跨模块数据修改,但每个“模块”有时会有单个上下文

外部上下文缓存在您安排期间检索到的实体。