具有多个dbcontext的一个事务

问题描述 投票:17回答:5

我正在单元测试中使用事务来回滚更改。单元测试使用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(),因此我仍然不了解该问题。

c# .net dbcontext transactionscope
5个回答
21
投票

在第一种情况下,您正在嵌套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对一个非常相似的问题有所帮助。


2
投票

更新:看来这个答案不清楚。建议不要使DbContexts保持尽可能长的生命。而是使用工作单位模式/想法。每个UOW有一个上下文。通常,这意味着每个HTTP请求,每个GUI交互或每个测试方法一个上下文。但是,如果需要,可以做不同的事情。


1
投票
此作品:

公共类Test1{public int ID {get;组; }公共字符串名称{get;组; }}


0
投票
对我来说,在配置文件中使用'Enlist = false'消除了DST错误,但也使事务无效,即使未达到scope scope.Complete(),数据库更改也将持久存在。

using (var scope = new TransactionScope()) using (var firstContext = new db1Context()) using (var secondContext = new db2Context()) { // do smth with dbContexts scope.Complete(); }


-1
投票
外部上下文正在缓存您在安排期间检索到的实体。
© www.soinside.com 2019 - 2024. All rights reserved.