Правильный способ использования 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.
Передайте свой
IDbTranasction
на ваш обычный вызов Dapper.До:
var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"});
После:
var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"}, transaction=tx);
Используйте новый
.Execute
метод расширения, который Dapper добавляет кIDbTransaction
сам:tx.Execute(sql, new {CustomerName = "Mark"});
Примечание : переменная
tx
происходит от
IDbTransaction tx = connection.BeginTransaction();
Вот как вы должны использовать транзакции с Dapper; ни один из них не является TranasctionScope.