Quelle est la bonne façon d'utiliser BeginTransaction()
avec IDbConnection
dans Dapper?
J'ai créé une méthode dans laquelle je dois utiliser BeginTransaction()
. Voici le code.
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 };
}
}
Quand j'ai exécuté la méthode ci-dessus - j'ai eu une exception -
Opération invalide. La connexion est fermée.
En effet, vous ne pouvez pas commencer une transaction avant que la connexion ne soit ouverte. Donc, quand j'ajoute cette ligne: cn.Open();
, l'erreur est résolue. Mais j’ai lu quelque part que l’ouverture manuelle de la connexion est mauvaise pratique !! _ Dapper ouvre une connexion uniquement quand il le faut.
Dans Entity Framework, vous pouvez gérer une transaction à l'aide de TransactionScope
.
Ma question est donc quelle est la bonne pratique pour gérer une transaction sans ajouter la ligne cn.Open()...
dans Dapper? Je suppose qu'il devrait y avoir un moyen approprié pour cela.
Ouvrir manuellement une connexion n'est pas une "mauvaise pratique"; dapper fonctionne avec des connexions ouvertes ou fermées comme commodité, rien de plus. Les personnes dont les connexions sont laissées ouvertes, inutilisées, pendant trop longtemps sans jamais les relâcher dans la piscine sont un piège courant. Cependant, dans la plupart des cas, cela ne pose pas de problème et vous pouvez certainement le faire:
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;
}
}
}
Notez que dapper a un paramètre facultatif à transmettre à la transaction, par exemple:
cn.Execute(sql, args, transaction: tran);
Je suis en fait tenté de créer des méthodes d'extension sur IDbTransaction
qui fonctionnent de la même manière, car une transaction expose toujours .Connection
; cela permettrait:
tran.Execute(sql, args);
Mais cela n'existe pas aujourd'hui.
TransactionScope
est une autre option, mais a une sémantique différente: cela peut impliquer le LTM ou le DTC, selon ... eh bien, la chance, principalement. Il est également tentant de créer un wrapper autour de IDbTransaction
qui n'a pas besoin de la variable try
/catch
- plutôt de la façon dont fonctionne TransactionScope
; quelque chose comme (cela n'existe pas non plus):
using(var cn = CreateConnection())
using(var tran = cn.SimpleTransaction())
{
tran.Execute(...);
tran.Execute(...);
tran.Complete();
}
Tu ne devrais pas appeler
cn.Close();
parce que le bloc using va essayer de fermer aussi. Pour la partie transaction, oui, vous pouvez également utiliser TransactionScope, puisqu'il ne s'agit pas d'une technique liée à Entity Framework . Regardez cette réponse SO: https://stackoverflow.com/a/6874617/566608 Il explique comment inscrire votre connexion dans l'étendue de la transaction . L'aspect important est: la connexion est automatiquement inscrite dans la transaction IIF vous ouvrez la connexion dans l'étendue .
Jetez un coup d'œil à Tim Schreiber solution qui est simple mais puissante et implémentée à l'aide d'un modèle de référentiel et qui a à l'esprit Dapper Transactions
.
La Commit()
dans le code ci-dessous le montre.
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);
}
}
Nous avons implémenté ce modèle Uow, mais nous avons des problèmes avec les appels asynchrones. Parfois, à _transaction.Dispose (), nous recevons La connexion ne prend pas en charge MultipleActiveResultSets.