Правильный способ использования BeginTransaction с Dapper.IDbConnection

Какой правильный способ использования BeginTransaction() с IDbConnection в Dapper?

Я создал метод, в котором я должен использовать BeginTransaction(), Вот код

using (IDbConnection cn = DBConnection)
{
    var oTransaction = cn.BeginTransaction();

    try
    {
        // SAVE BASIC CONSULT DETAIL
        var oPara = new DynamicParameters();
        oPara.Add("@PatientID", iPatientID, dbType: DbType.Int32);
        ..........blah......blah............
    }
    catch (Exception ex)
    {
        oTransaction.Rollback();
        return new SaveResponse { Success = false, ResponseString = ex.Message };
    }
}

Когда я выполнил выше метод - я получил исключение -

Неверная операция. Соединение закрыто.

Это потому, что вы не можете начать транзакцию до открытия соединения. Поэтому, когда я добавлю эту строку: cn.Open();, ошибка будет устранена. Но я где-то читал, что открывать соединение вручную - плохая практика!! Dapper открывает соединение только тогда, когда это необходимо.

В Entity Framework вы можете обрабатывать транзакции, используя TransactionScope,

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

5 ответов

Решение

Открытие соединения вручную не является "плохой практикой"; dapper работает с открытыми или закрытыми соединениями для удобства, не более того. Обычная проблема - люди, имеющие соединения, которые остаются открытыми, неиспользованными, слишком долго, даже не выпуская их в пул - однако, в большинстве случаев это не проблема, и вы, безусловно, можете сделать:

using(var cn = CreateConnection()) {
    cn.Open();
    using(var tran = cn.BeginTransaction()) {
        try {
            // multiple operations involving cn and tran here

            tran.Commit();
        } catch {
            tran.Rollback();
            throw;
        }
    }
}

Обратите внимание, что dapper имеет необязательный параметр для передачи в транзакции, например:

cn.Execute(sql, args, transaction: tran);

Я на самом деле соблазн сделать методы расширения на IDbTransaction которые работают аналогично, так как транзакция всегда выставляет.Connection; это позволило бы:

tran.Execute(sql, args);

Но этого не существует сегодня.

TransactionScope это другой вариант, но он имеет другую семантику: это может включать LTM или DTC, в зависимости от... ну, в основном, удачи. Также заманчиво создать обертку вокруг IDbTransaction это не нужно try/catch - больше похоже на то, как TransactionScope работает; что-то вроде (этого тоже не существует):

using(var cn = CreateConnection())
using(var tran = cn.SimpleTransaction())
{
    tran.Execute(...);
    tran.Execute(...);

    tran.Complete();
}

Вы не должны звонить

cn.Close();

потому что использующий блок будет пытаться закрыть тоже. Для части транзакции, да, вы также можете использовать TransactionScope, так как это не относится к технике Entity Framework. Посмотрите на этот SO-ответ: /questions/26358553/dapper-and-transactionscope/26358560#26358560 В нем объясняется, как подключить ваше соединение к области транзакции. Важный аспект: соединение автоматически зачисляется в транзакцию, если вы открываете соединение внутри области действия.

Взгляните на решение Тима Шрайбера, которое простое, но мощное и реализовано с использованием шаблона репозитория. Dapper Transactions в уме.

Commit() в коде ниже показывает это.

public class UnitOfWork : IUnitOfWork
{
    private IDbConnection _connection;
    private IDbTransaction _transaction;
    private IBreedRepository _breedRepository;
    private ICatRepository _catRepository;
    private bool _disposed;

    public UnitOfWork(string connectionString)
    {
        _connection = new SqlConnection(connectionString);
        _connection.Open();
        _transaction = _connection.BeginTransaction();
    }

    public IBreedRepository BreedRepository
    {
        get { return _breedRepository ?? (_breedRepository = new BreedRepository(_transaction)); }
    }

    public ICatRepository CatRepository
    {
        get { return _catRepository ?? (_catRepository = new CatRepository(_transaction)); }
    }

    public void Commit()
    {
        try
        {
            _transaction.Commit();
        }
        catch
        {
            _transaction.Rollback();
            throw;
        }
        finally
        {
            _transaction.Dispose();
            _transaction = _connection.BeginTransaction();
            resetRepositories();
        }
    }

    private void resetRepositories()
    {
        _breedRepository = null;
        _catRepository = null;
    }

    public void Dispose()
    {
        dispose(true);
        GC.SuppressFinalize(this);
    }

    private void dispose(bool disposing)
    {
        if (!_disposed)
        {
            if(disposing)
            {
                if (_transaction != null)
                {
                    _transaction.Dispose();
                    _transaction = null;
                }
                if(_connection != null)
                {
                    _connection.Dispose();
                    _connection = null;
                }
            }
            _disposed = true;
        }
    }

    ~UnitOfWork()
    {
        dispose(false);
    }
}

Мы реализовали этот шаблон Uow, но у нас есть проблемы с асинхронными вызовами. Иногда в _transaction.Dispose() мы получаем Соединение не поддерживает MultipleActiveResultSets.

Есть два предполагаемых способа использования транзакций с Dapper.

  1. Передайте свой IDbTranasctionна ваш обычный вызов Dapper.

    До:

            var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"});
    

    После:

            var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"}, transaction=tx);
    
  2. Используйте новый .Executeметод расширения, который Dapper добавляет к IDbTransactionсам:

            tx.Execute(sql, new {CustomerName = "Mark"});
    

Примечание : переменная txпроисходит от IDbTransaction tx = connection.BeginTransaction();

Вот как вы должны использовать транзакции с Dapper; ни один из них не является TranasctionScope.

Бонусное чтение

Другие вопросы по тегам