Вопрос по transactions, entity-framework – Подготовка к нескольким контекстам EF на единицу работы - TransactionScope

3

Я думаю о вариантах реализации единой единицы работы для работы с несколькими источниками данных - структурой сущностей. Я придумал предварительный подход - пока что он имеет дело с одним контекстом - но, по-видимому, это не очень хорошая идея.

Если бы мы проанализировали приведенный ниже код, вы бы посчитали его плохой реализацией? Является ли время существования области транзакции потенциальной проблемой?

Конечно, если мы обернем область транзакции различными контекстами, мы будем защищены, если второй контекст.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   ответ
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(); 
    }
}

Эта версия отделяет операцию сохранения от сброса внутреннего состояния контекста. Причина состоит в том, что если первый контекст успешно сохраняет изменения, но второе вызывает исключение, транзакция будет откатываться. Поэтому мы не хотим, чтобы первый контекст уже очистил все изменения как принятые (мы потеряем информацию о выполненных изменениях и не сможем сохранить их снова).

Идея запуска TransactionScope в конструкторе единиц работы заключается в том, что когда вы добавляете объект в контекст, вы можете извлечь его из контекста даже до того, как внесете изменения в базу данных. Так, например, если я позвонюunitOfWork.Context.Employees.AddObject(employee), затем позвониcontext.SaveChanges() а затем без завершения единицы работыcontext.Employees.ToList() вернет только что вставленного сотрудника, даже если он не существует в БД. Joao Milasch
Это довольно плохая идея, потому что если вы вызываете SaveChanges, запись вставляется в базу данных, и эта таблица блокируется до тех пор, пока вы не совершите или не откатите транзакцию, чтобы другой поток не смог прочитать или записать эту таблиц Ladislav Mrnka
+ 1 @LadislavMrnka БудетTransactionScope требуется только в том случае, если сохранение в базы данных должно выполняться в определенном порядке? Другими словами, если мои множественные контексты не связаны, было бы хорошо просто позвонить_contextA.SaveChanges(); _contextB.SaveChanges(); в моем UoW? GFoley83
@ GFoley83: Это зависит от того, хотите ли вы, чтобы эти вызовы были атомарной транзакцией. Если вы используетеTransactionScope оба вызова должны быть успешными. Если второй вызов не удался, первый откатывается. Без объема транзакции первый вызов изменит базу данных, даже если второй вызов завершится неудачей. Ladislav Mrnka
@ LadislavMrnka: Отлично, именно то, что мне было нужно. Вы как папа Entity Framework здесь, в стеке! Спасибо GFoley83

Похожие вопросы