2014-07-09 9 views
18

Qual è il modo corretto di utilizzare BeginTransaction() con IDbConnection in Dapper?Modo corretto di utilizzare BeginTransaction con Dapper.IDbConnection

Ho creato un metodo in cui devo usare BeginTransaction(). Ecco il codice.

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 }; 
    } 
} 

Quando ho eseguito metodo di cui sopra - ho ottenuto un'eccezione -

Operazione non valida. La connessione è chiusa.

Questo perché non è possibile iniziare una transazione prima che la connessione venga aperta. Quindi quando aggiungo questa riga: cn.Open();, l'errore viene risolto. Ma ho letto da qualche parte che l'apertura manuale della connessione è una cattiva pratica !! Dapper apre una connessione solo quando è necessario.

In Entity framework è possibile gestire una transazione utilizzando uno TransactionScope.

Quindi la mia domanda è che cosa è una buona pratica per gestire la transazione senza aggiungere la riga cn.Open()... in Dapper? Immagino che ci dovrebbe essere un modo corretto per questo.

risposta

48

Aprire manualmente una connessione non è "cattiva pratica"; dapper funziona con connessioni aperte o chiuse come convenienza, niente di più. Un Gotcha comune è persone che hanno le connessioni che vengono lasciati aperti, inutilizzato, per troppo tempo senza mai rilasciare alla piscina - tuttavia, questo non è un problema nella maggior parte dei casi, e si può certamente fare:

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; 
     } 
    } 
} 

Si noti che Dapper ha un parametro opzionale per passare nella transazione, ad esempio:

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

sono davvero tentati per rendere metodi di estensione su IDbTransaction che funzionano in modo simile, dal momento che a transaction always exposes .Connection; ciò consentirebbe:

tran.Execute(sql, args); 

Ma questo non esiste oggi.

TransactionScope è un'altra opzione, ma ha semantica diversa: questo potrebbe coinvolgere l'LTM o il DTC, a seconda ... beh, fortuna, soprattutto.Si è anche tentati di creare un wrapper intorno a IDbTransaction che non ha bisogno dello try/catch - più come funziona come TransactionScope; qualcosa di simile (anche questo non esiste):

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

    tran.Complete(); 
} 
+0

FFR: questo è stato suggerito ma rifiutato come PR :(https://github.com/StackExchange/dapper-dot-net/pull/429 Marc ha anche partecipato al discussione, è stato rifiutato principalmente perché c'è già una duplicazione tra sync/async - l'aggiunta di metodi di estensione per le transazioni comporterebbe la duplicazione di tutti i metodi 4 volte –

+2

@ marc-gravell - Nel caso del rollback, devi chiamare esplicitamente ' tran.RollBack'? è la transazione non ripristinata automaticamente a disposizione? – MaYaN

5

Non si dovrebbe chiamare

cn.Close(); 

perché il blocco utilizzando proverà a chiudere troppo. Per la parte della transazione, sì, è possibile utilizzare anche TransactionScope, poiché non è una tecnica correlata a Entity Framework. Dai un'occhiata a questa risposta SO: https://stackoverflow.com/a/6874617/566608 Spiega come integrare la tua connessione nell'ambito della transazione. L'aspetto importante è: la connessione viene automaticamente inserita nella transazione IIF si apre la connessione all'interno dello scope.

+0

Sì, hai ragione, mi dispiace ho dimenticato di rimuoverlo. Quindi il link che hai fornito diceva che puoi usare TransactionScope con Dapper ma devi scrivere questo codice - ** con.Open() **. Quindi è una buona pratica ?? –

+0

ovviamente devi aprire la connessione prima di usarlo –

1

Date un'occhiata a Tim Schreiber soluzione che è semplice ma potente e implementato utilizzando Pattern Repository e ha Dapper Transactions in mente.

Lo Commit() nel codice qui sotto lo mostra.

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); 
    } 
} 
+0

È bello. Avere diverse domande sulla soluzione. Cosa succede se non voglio usare le transazioni diciamo per le solite query selezionate? Quindi, come ho capito, sql genererà il codice per transazioni dopo commit() o cosa? Perché ho bisogno di fare BeginTransaction() se non lo userò nella query? Può influenzare la perfomance per le query in cui faccio n bisogno di transazioni? Per favore, non mi capisci male. Voglio solo chiarire tutte le cose prima di iniziare a usare questo in produzione. –

+0

Quindi, penso che sia meglio aggiungere flag (useTransaction = false). In tal caso, creando istanza di unitOfWork, possiamo scegliere la strategia di cui abbiamo bisogno. Ho ragione? –

+0

Non è necessario 'commit()' quando la query è solo 'SELECT'. Quindi non preoccuparti delle prestazioni !. la tua idea sull'aggiunta di una bandiera è buona, ma in realtà non è necessaria. Lo uso in questo modo e funziona come un fascino. – vaheeds

0

Abbiamo implementato questo modello Uow, ma abbiamo problemi con le chiamate asincrone. A volte su _transaction.Dispose() riceviamo La connessione non supporta MultipleActiveResultSets.

Problemi correlati