我正在单元测试中使用事务来回滚更改。单元测试使用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!");
}
}
}
因此,基本上,我有一个解决此问题的方法(在编写此问题时曾考虑过),但我仍然想知道为什么嵌套dbcontext时无法访问事务中未提交的数据。难道因为(dbcontext)就像事务本身一样吗?如果是这样,由于我正在内部dbcontext上调用.SaveChanges(),因此我仍然不了解该问题。
在第一种情况下,您正在嵌套DbContexts
。将为每个数据库打开与数据库的连接。当您在using
块中调用服务方法时,将在TransactionScope
中打开一个新连接,而另一个连接已经打开。这将导致您的事务升级到distributed transaction,并且外部连接无法使用部分提交的数据(服务中DbContext.SaveChanges
调用的结果)。还请注意,分布式事务处理要慢得多,因此,这会降低性能。]
在第二种情况下,当您打开和关闭三个连接时,在您的事务中同时只能打开一个连接。由于这些连接共享same connection string
,因此事务不会自动升级为分布式连接,因此,事务中的每个后续连接都可以访问由先前连接执行的更改。您可以尝试将Enlist=false
参数添加到您的连接字符串中。这将禁用分布式事务中的自动登记,从而导致在第一种情况下引发异常。如果您使用的是SQL Server 2008及更高版本,则第二种方案将保持正常工作,因为该事务不会得到提升。 (Prior versions of SQL Server will still promote the transaction in this scenario.)
您可能还会发现this great answer对一个非常相似的问题有所帮助。
更新:看来这个答案不清楚。建议不要使DbContexts保持尽可能长的生命。而是使用工作单位模式/想法。每个UOW有一个上下文。通常,这意味着每个HTTP请求,每个GUI交互或每个测试方法一个上下文。但是,如果需要,可以做不同的事情。
公共类Test1{public int ID {get;组; }公共字符串名称{get;组; }}
using (var scope = new TransactionScope())
using (var firstContext = new db1Context())
using (var secondContext = new db2Context())
{
// do smth with dbContexts
scope.Complete();
}