TransactionScope за пределами использования блока? - PullRequest
0 голосов
/ 12 декабря 2018

Мой запрос немного сложнее, чем позволяет заголовок.Возможно, это глупый вопрос, но я не мог найти какой-то определенный ответ при поиске в Интернете.В настоящее время я реализую шаблон Repository / Unit-of-Work в своем собственном стиле, и он выглядит примерно так:

// Note: methods are async for conventions, not because
// they're truly async
public interface IUnitOfWork
{
    Task Begin();

    // The Task<int> is the numbers of rows affected by this commit
    Task<int> Commit();

    Task Rollback();
}

Хранилище можно более или менее выразить так:

public interface IWriteableRepository<T>
    where T : class
{
     EntityEntry<T> Insert(T item);

     // Other CRUD methods removed for brevity; they're
     // of similar signatures
}

Идея состоит в том, что IUnitOfWork будет удерживать некоторый экземпляр TransactionScope внутри и обрабатывать соответствующую логику.

У меня тогда есть две проблемы.Во-первых, если каждый экземпляр IUnitOfWork и IWriteableRepository<T> внедряется с различными экземплярами DbContext (я пока использую EntityFrameworkCore), вызовет ли DbContext.BeginTransactionAsync() область транзакции для обоих в следующем коде?

await this.UnitOfWork.Begin();

this.Repository.Insert(someEntity);

var rows = await this.UnitOfWork.Commit();

Другими словами, работает ли хранилище только с транзакцией, созданной в вызове к Begin(), или оно будет работать совершенно независимо?

Вторая проблема, с которой я столкнулся, заключается вотношение к реализации интерфейса IUnitOfWork.Пока что мой подход был примерно

public class UnitOfWork : IUnitOfWork
{
    public UnitOfWork(DbContext context)
    {
        this.Context = context;
    }

    private DbContext Context { get; set; }

    private TransactionScope Transaction { get; set; }

    public async Task Begin()
    {
        if (this.Scope == null)
        {
            this.Transaction = await this.Context
                .Database
                .BeginTransactionAsync();
        }
    }

    public async Task<int> Commit()
    {
        if (this.Scope != null)
        {
            var rows = await this.Context.SaveChangesAsync(false);

            this.Scope.Commit();

            this.Context.AcceptAllChanges();

            return rows;
        }
    }

    public Task Rollback()
    {
        if (this.Scope != null)
        {
            this.Scope.Rollback();
            this.Scope.Dispose();

            this.Scope = null;
        }

        return Task.CompletedTask;
    }
}

. Я в основном не уверен, можно ли улучшить метод Rollback().Я чувствую, что выбрасывать объект явно не правильно.Есть ли какой-то другой способ справиться с избавлением от TransactionScope?

1 Ответ

0 голосов
/ 05 января 2019

В моем случае, это решение, которое я придумала - я бы определенно не рекомендовал следовать ему, и я уверен, что есть уловки, которые моя команда должна будет решить, когда они появятся ...

Поскольку нам требуется несколько механизмов баз данных (Mongo и EF / SQL), мы обернули наши взаимодействия с базой данных в шаблон репозитория и единицы работы.Все наши репозитории реализованы для каждого механизма базы данных, например, IMongoRepository<T> : IWriteableRepository<T>, а методы, которые не могут быть абстрагированы с помощью IWriteableRepository<T>, реализованы с помощью IMongoRepository<T>.Это работает довольно хорошо, и я не возражаю против использования этого шаблона.

IUnitOfWork также реализован для каждого механизма базы данных, потому что Mongo, SQL и т. Д. Будут обрабатывать транзакции по-разному.Забота о совместном использовании контекстов при поддержке объектов для инъекций была решена с помощью фабрики, то есть что-то вроде

public class FooService
{
    public FooService(
        IUnitOfWorkFactory<EntityFrameworkUnitOfWork> factory,
        IRepositoryContext context,
        IWriteableRepository<Bar> repository)
    {
        this.UnitOfWorkFactory = factory;
        this.Context = context;
        this.Repository = repository;
    }

    private IUnitOfWorkFactory<EntityFrameworkUnitOfWork> UnitOfWorkFactory { get; set; }

    private IRepositoryContext Context { get; set; }

    private IWriteableRepository<Bar> Repository { get; set; }

    public bool RemoveBar(int baz)
    {
        // IUnitOfWorkFactory<T>.Begin(IRepositoryContext)
        //     where T : IUnitOfWork, new()
        // 
        // 1) Creates a new IUnitOfWork instance by calling parameterless constructor
        // 2) Call UseContext(IRepositoryContext) on UoW, passing in the context;
        //        This causes the UoW to use the passed-in context
        // 3) Calls .Begin() on the UoW
        // 4) Returns the UoW
        using (var unitOfWork = this.UnitOfWorkFactory.Begin(this.Context))
        {
            var bar = this.Repository
                .Query()
                .First(x => x.Baz == baz);

            this.Repository.Remove(bar);

            var (success, rows) = unitOfWork.Commit();

            return success && rows > 0;
        }
    }
}

EntityFrameworkUnitOfWork (или любой IUnitOfWork) разрешено реализовывать Begin,Commit и Rollback как хочет.IUnitOfWork также реализует IDisposable для обеспечения очистки базовых объектов транзакции.Использование того же контекста также гарантирует, что транзакции будут обязательно применяться к репозиториям, использующим этот контекст.

Кроме того, IUnitOfWork вместо фабрики может быть передано, если у него есть проверки, чтобы гарантировать, что у него только одна транзакцияоткрывать одновременно;но, чтобы удалить эту связь с реализацией, мы создали фабрику.Это не только гарантирует, что у нас есть только одна транзакция на каждый using блок, но и мы имеем возможность иметь using блок, не касаясь конструктора для IUnitOfWork в нашем потребляющем коде.

Как заявление об отказе от ответственности, я искренне согласен с тем, что вы не должны помещать ORM в репозитории.Это запутает ваш код БД и добавит ненужные сложности.Мы используем эти репозитории, чтобы сделать наши взаимодействия с базами данных независимыми при выполнении простых задач.В противном случае мы получаем более конкретную реализацию наших репозиториев, удаляя большую часть волшебства, от которого страдают другие реализации шаблонов репозиториев.Как правило, когда методы выходят за рамки операций с индивидуальной записью, механизмы и драйверы баз данных имеют разные идеи, как это сделать.

Последнее замечание: может быть очевидно, что если вы внедрите репозиторий, который не подходитс вводимым контекстом (например, IMongoContext и IEntityFrameworkRepository<Bar>) ваш код не будет выполняться в транзакциях с базой данных.Причина, по которой это не представляет проблемы, заключается в том, что

  1. , использующий контекст, отличный от контекста в вашем хранилище, в большинстве случаев уже бессмысленен
  2. вы должны управлять контекстом и хранилищем в потребляющемкод, и, следовательно, будет знать о конфликтующих контекстах
...