【问题标题】:Preparing for multiple EF contexts on a unit of work - TransactionScope在一个工作单元上准备多个 EF 上下文 - TransactionScope
【发布时间】:2012-05-23 23:15:40
【问题描述】:

我正在考虑实现单个工作单元来处理多个数据源的选项 - 实体框架。我想出了一个试探性的方法——现在处理一个单一的上下文——但这显然不是一个好主意。

如果我们分析下面的代码,您会认为这是一个糟糕的实现吗?事务范围的生命周期是一个潜在的问题吗?

当然,如果我们用不同的上下文包装事务范围,如果第二个 context.SaveChanges() 失败,我们就会被覆盖...

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Transactions;

    namespace ConsoleApplication2
    {
        class Program
        {
            static void Main(string[] args)
            {
                using(UnitOfWork unitOfWork = new UnitOfWork())
                {

                    var repository = new EmployeeRepository(unitOfWork);

                    var employee = repository.CreateOrGetEmployee("Whatever Name");

                    Console.Write(employee.Id);

                    unitOfWork.SaveChanges();
                }
            }
        }

        class UnitOfWork : IDisposable
        {
            TestEntities _context;
            TransactionScope _scope;
            public UnitOfWork()
            {
                _scope = new TransactionScope();
                _context = new TestEntities();
            }

            public void SaveChanges()
            {
                _context.SaveChanges();
                _scope.Complete();
            }

            public TestEntities Context
            {
                get
                {
                    return _context;
                }
            }

            public void Dispose()
            {
                _scope.Dispose();
                _context.Dispose();
            }
        }

        class EmployeeRepository
        {
            UnitOfWork _unitOfWork;

            public EmployeeRepository(UnitOfWork unitOfWork)
            {
                _unitOfWork = unitOfWork;
            }

            public Employee GetEmployeeById(int employeeId)
            {
                return _unitOfWork.Context.Employees.SingleOrDefault(e => e.Id == employeeId);
            }

            public Employee CreateEmployee(string fullName)
            {
                Employee employee = new Employee();
                employee.FullName = fullName;
                _unitOfWork.Context.SaveChanges();
                return employee;
            }

            public Employee CreateOrGetEmployee(string fullName)
            {
                var employee = _unitOfWork.Context.Employees.FirstOrDefault(e => e.FullName == fullName);
                if (employee == null)
                {
                    employee = new Employee();
                    employee.FullName = fullName;
                    this.AddEmployee(employee);
                }
                return employee;
            }

            public Employee AddEmployee(Employee employee)
            {
                _unitOfWork.Context.Employees.AddObject(employee);
                _unitOfWork.Context.SaveChanges();
                return employee;
            }
        }
    }

【问题讨论】:

    标签: entity-framework transactions transactionscope unit-of-work


    【解决方案1】:

    为什么在构造函数中启动TransactionScope?您只需要它来保存更改。

    public void SaveChanges()
    {
        // SaveChanges also uses transaction which uses by default ReadCommitted isolation
        // level but TransactionScope uses by default more restrictive Serializable isolation
        // level 
        using (var scope = new TransactionScope(TransactionScopeOption.Required,
                                                new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
        {
            _context.SaveChanges();
            scope.Complete();
        }
    }
    

    如果您想拥有具有更多上下文的工作单元,您只需将所有这些上下文包装在同一个工作单元类中即可。你的SaveChanges 会变得有点复杂:

    public void SaveChanges()
    {
        using (var scope = new TransactionScope(TransactionScopeOption.Required,
                                                new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
        {
            _contextA.SaveChanges(SaveOptions.DetectChangesBeforeSave);
            _contextB.SaveChanges(SaveOptions.DetectChangesBeforeSave);
            scope.Complete();
            _contextA.AcceptAllChanges();
            _contextB.AcceptAllChanges(); 
        }
    }
    

    此版本将保存操作与重置上下文的内部状态分开。原因是如果第一个上下文成功保存更改但第二个触发异常,则事务将回滚。因此,我们不希望第一个上下文已将所有更改清除为已接受(我们会丢失有关已执行更改的信息,并且我们将无法再次保存它们)。

    【讨论】:

    • 在工作单元构造函数中启动 TransactionScope 的想法是,当您将对象添加到上下文时,您甚至可以在将更改提交到数据库之前从上下文中检索它。例如,如果我调用unitOfWork.Context.Employees.AddObject(employee),然后调用context.SaveChanges(),然后在没有完成工作单元的情况下context.Employees.ToList() 将返回刚刚插入的员工,即使它在数据库中不存在。
    • 这是一个非常糟糕的主意,因为如果您调用 SaveChanges,记录将被插入到数据库中,并且该表将被锁定,直到您提交或回滚事务,因此其他线程将无法读取或写入该表。
    • +1 @LadislavMrnka 只有在必须以特定顺序保存到数据库时才需要TransactionScope?换句话说,如果我的多个上下文不相关,可以在我的 UoW 中调用 _contextA.SaveChanges(); _contextB.SaveChanges(); 吗?
    • @GFoley83:这取决于您是否希望这些调用成为原子事务。如果您使用TransactionScope,则两个调用都必须成功。如果第二个调用失败,第一个调用将回滚。如果没有事务范围,即使第二次调用失败,第一次调用也会修改数据库。
    • @LadislavMrnka:太棒了,正是我需要的。你就像这里的实体框架教皇一样!谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多