准备工作单元上的多个EF上下文 - TransactionScope

时间:2012-05-23 23:15:41

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

我正在考虑实施处理多个数据源的单一工作单元的选项 - 实体框架。我想出了一个尝试性的方法 - 现在处理一个单一的背景 - 但它显然不是一个好主意。

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

当然,如果我们用不同的上下文包装事务范围,如果第二个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;
            }
        }
    }

1 个答案:

答案 0 :(得分:6)

为什么要在构造函数中启动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(); 
    }
}

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