Правильный способ использования 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 вы можете обрабатывать транзакцию с помощью TransactionScope
.
Итак, мой вопрос - хорошая практика для обработки транзакции без добавления строки cn.Open()...
в Dapper? Я предполагаю, что для этого должен быть правильный путь.
Ответы
Ответ 1
Вручное открытие соединения - это не "плохая практика"; 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();
}
Ответ 2
Вы не должны звонить
cn.Close();
потому что блок использования попытается закрыть тоже.
Для части транзакции да, вы также можете использовать TransactionScope, так как это не связанная с Entity Framework технология.
Взгляните на этот ответ SO: fooobar.com/questions/146477/...
В нем объясняется, как привлечь ваше соединение в области транзакций.
Важным аспектом является: соединение автоматически заносится в транзакцию IIF, вы открываете соединение внутри области.
Ответ 3
Посмотрите решение Тима Шрайбера, которое просто, но мощно и реализовано с использованием шаблона репозитория и имеет Dapper Transactions
. p >
В приведенном ниже коде 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);
}
}
Ответ 4
Мы реализовали этот шаблон Uow, но у нас есть проблемы с асинхронными вызовами. Иногда в _transaction.Dispose() мы получаем Соединение не поддерживает MultipleActiveResultSets.