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

joh*_*son 3 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;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Lad*_*nka 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您希望具有更多上下文的工作单元,则只需将所有这些上下文包装在同一工作单元中.你的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(); 
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这是非常糟糕的主意,因为如果调用SaveChanges,则会将记录插入到数据库中,并且该表将被锁定,直到您提交或回滚事务,因此其他线程将无法读取或写入该表. (3认同)
  • @ GFoley83:这取决于你是否希望这些调用是原子事务.如果你使用`TransactionScope`,两个调用都必须成功.如果第二次调用失败,则第一次调用将被回滚.如果没有事务范围,即使第二次调用失败,第一次调用也将修改数据库. (2认同)