c# - framework - dapper.net core
Forma correcta de usar BeginTransaction con Dapper.IDbConnection (4)
Eche un vistazo a la solución de Tim Schreiber que es simple pero poderosa e implementada utilizando un patrón de repositorio y que tiene en mente las Dapper Transactions
.
El Commit()
en el código siguiente lo muestra.
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);
}
}
¿Cuál es la forma correcta de usar BeginTransaction()
con IDbConnection
en Dapper?
He creado un método en el que tengo que usar BeginTransaction()
. Aquí está el código.
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 };
}
}
Cuando ejecuté el método anterior, obtuve una excepción
Operación inválida. La conexión está cerrada.
Esto se debe a que no puede comenzar una transacción antes de que se abra la conexión. Así que cuando agrego esta línea: cn.Open();
, el error se resuelve. ¡Pero he leído en alguna parte que abrir la conexión manualmente es una mala práctica! Dapper abre una conexión solo cuando es necesario.
En Entity framework puede manejar una transacción usando un TransactionScope
.
Entonces, mi pregunta es ¿cuál es una buena práctica para manejar transacciones sin agregar la línea cn.Open()...
en Dapper ? Supongo que debería haber alguna forma adecuada para esto.
Implementamos este patrón de Uow, pero tenemos problemas con las llamadas asíncronas. A veces, en _transaction.Dispose () recibimos La conexión no es compatible con MultipleActiveResultSets.
La apertura manual de una conexión no es una "mala práctica"; Dapper trabaja con conexiones abiertas o cerradas como una conveniencia , nada más. Un problema común es que las personas tengan conexiones que permanecen abiertas, sin uso, durante demasiado tiempo sin liberarlas nunca al grupo. Sin embargo, esto no es un problema en la mayoría de los casos, y ciertamente puede hacerlo:
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;
}
}
}
Tenga en cuenta que Dapper tiene un parámetro opcional para pasar en la transacción, por ejemplo:
cn.Execute(sql, args, transaction: tran);
Estoy realmente tentado a hacer que los métodos de extensión en IDbTransaction
funcionen de manera similar, ya que una transacción siempre expone .Connection
; esto permitiría:
tran.Execute(sql, args);
Pero esto no existe hoy.
TransactionScope
es otra opción, pero tiene diferentes semánticas: esto podría involucrar el LTM o el DTC, dependiendo de ... bueno, suerte, principalmente. También es tentador crear una envoltura alrededor de IDbTransaction
que no necesita el try
/ catch
, más como cómo funciona TransactionScope
; algo así (esto tampoco existe):
using(var cn = CreateConnection())
using(var tran = cn.SimpleTransaction())
{
tran.Execute(...);
tran.Execute(...);
tran.Complete();
}
No deberias llamar
cn.Close();
porque el bloque de uso intentará cerrar también. Para la parte de la transacción, sí, también puede usar TransactionScope, ya que no es una técnica relacionada con Entity Framework. Echa un vistazo a esta respuesta SO: https://.com/a/6874617/566608 Explica cómo incluir tu conexión en el ámbito de la transacción. El aspecto importante es: la conexión se inscribe automáticamente en la transacción IIF, se abre la conexión dentro del alcance .