Entity Framework 6 operazioni asincrone e TranscationScope

Cerco su StackOverflow ma non riesco a trovare una domanda simile, per favore indicami se ce n’è già una.

Stavo cercando di implementare un repository generico riutilizzabile con entrambe le operazioni di sincronizzazione e asincrono ma con le mie poche conoscenze con Entity Framework e Unit Of Work sto faticando a trovare il modo corretto di implementarlo.

Ho aggiunto alcune variazioni sull’operazione SaveAndCommit ma non so quale sia il modo migliore per farlo con transazione e asincrono.

—-Modificare—-

Secondo la mia comprensione, le transazioni dovrebbero essere utilizzate quando vengono eseguite più operazioni, ma per scopi di comprensione l’ho usata per una sola operazione. (Per favore correggimi se sbaglio)

Questo è quello che ho fatto finora

public class Service : IService where TEntity : Entity { #region Constructor and Properties UnitOfWork _unitOfWork { get { return UnitOfWork.UnitOfWorkPerHttpRequest; } } protected DbSet Entities { get { return _unitOfWork.Set(); } } #endregion Constructor and Properties #region Operations public virtual IQueryable QueryableEntities() { return Entities; } public virtual async Task<IList> WhereAsync(Expression<Func> predicate) { return await Entities.Where(predicate).ToListAsync(); } public virtual IList Where(Expression<Func> predicate) { return Entities.Where(predicate).ToList(); } public virtual async Task FirstOrDefaultAsync(Expression<Func> predicate) { return await Entities.FirstOrDefaultAsync(predicate); } public virtual TEntity FirstOrDefault(Expression<Func> predicate) { return Entities.FirstOrDefault(predicate); } public virtual async Task GetByIdAsync(int id) { return await Entities.FindAsync(id); } public virtual TEntity GetById(int id) { return Entities.Find(id); } // Method to the change the EntityState public virtual void Save(TEntity entity) { if (entity.Id == 0) { Entities.Add(entity); } else { _unitOfWork.Entry(entity).State = EntityState.Modified; } } #region Need clarification here // Uses transaction scope to commit the entity and dispose automatically // call rollback but this is not async and don't have any async // functions (Or I could not find) public virtual void SaveAndCommit(TEntity entity) { using (var transaction = _unitOfWork.BeginTransaction()) { try { Save(entity); transaction.Commit(); } catch (DbEntityValidationException e) { } } } // This is asynchronous but don't uses transaction public virtual async Task SaveAndCommitAsync(TEntity entity) { try { Save(entity); await _unitOfWork.SaveChangesAsync(); } catch (DbEntityValidationException e) { } } // Tried to mix async and transaction but don't know if it will actually // work or correct way of doing this public virtual async Task SaveAndCommitWithTransactionAsync(TEntity entity) { using (var transaction = _unitOfWork.BeginTransaction()) { try { Save(entity); await _unitOfWork.SaveChangesAsync(); } catch (DbEntityValidationException e) { transaction.Rollback(); } } } #endregion Need clarification here public virtual async Task DeleteAsync(TEntity entity) { if (entity == null) return; Entities.Remove(entity); await _unitOfWork.SaveChangesAsync(); } //All similar methods for delete as for Save public virtual async Task CountAsync(Expression<Func> predicate = null) { if (predicate != null) { return await Entities.CountAsync(predicate); } return await Entities.CountAsync(); } #endregion Operations } 

Per favore guidami e suggerisci il modo migliore per raggiungere questo objective.


Ora sembra che sarebbe il modo corretto di implementare un ambito di transazione con chiamata asincrona

 public virtual async Task SaveAndCommitWithTransactionAsync(TEntity entity) { using (var transaction = _unitOfWork.BeginTransaction()) { Save(entity); await _unitOfWork.SaveChangesAsync(); // Still no changes made to database transaction.Commit(); //Rollback will automatically be called by using in dispose method } } 

Riferimenti Riferimenti MSDN

Blog con una descrizione più chiara

visualstudiomagazine.com Per: quando chiami SaveChanges, nessuna delle tue modifiche diventerà effettiva finché non chiami il metodo Commit dell’object Transaction

Modificare:

Affinché gli ambiti di transazione possano funzionare insieme con async-await , a partire da .NET 4.5.1 è ansible passare un flag TransactionScopeAsyncFlowOption.Enabled al suo costruttore:

 using (var scope = new TransactionScope(... , TransactionScopeAsyncFlowOption.Enabled)) 

Questo assicura che gli ambiti di transazione si comportino bene con le continuazioni. Vedi Ottieni TransactionScope per lavorare con async / attendi altro.

Nota questa funzione è disponibile da .NET 4.5.1 in poi.

Modifica 2:

Ok, dopo il commento @Jcl su BeingTransaction , ho cercato e ho trovato questa risposta :

Con l’introduzione di EF6, Microsoft consiglia di utilizzare nuovi metodi API : Database.BeginTransaction() e Database.UseTransaction() . System.Transactions.TransactionScope è solo un vecchio stile di scrittura del codice transazionale.

Ma Database.BeginTransaction() viene utilizzato solo per transazioni di operazioni correlate al database , mentre System.Transactions.TransactionScope rende anche il ansible ‘codice C # semplice’ transazionale .

Limitazioni di nuove funzionalità asincrone di TransactionScope :

  • Richiede .NET 4.5.1 o versioni successive per lavorare con metodi asincroni.

  • Non può essere utilizzato in scenari cloud se non si è sicuri di disporre di una sola connessione (gli scenari cloud non supportano la distribuzione
    transazioni).

  • Non può essere combinato con l’approccio Database.UseTransaction() delle sezioni precedenti.

  • Genera eccezioni se emetti DDL (es. A causa di a
    Database Initializer) e non hanno abilitato le transazioni distribuite
    attraverso il servizio MSDTC.

Sembra che il nuovo approccio che inizia con EF6 e sopra sia quello di utilizzare Database.BeginTransaction() invece di TransactionScope , date le limitazioni.

Concludere:

Questo è il modo corretto per scrivere chiamate db con scope transazionale della transazione:

 public virtual async Task SaveAndCommitWithTransactionAsync(TEntity entity) { using (var transaction = _unitOfWork.BeginTransaction()) { try { Save(entity); await _unitOfWork.SaveChangesAsync(); transaction.Commit(); } catch (DbEntityValidationException e) { } } } 

Si noti che transaction.RollBack() non deve essere richiamato nel caso in cui l’ambito sia racchiuso in un’istruzione using , poiché sarà necessario il rollback se il commit non ha avuto esito positivo.

Una domanda correlata: rollback della transazione Entity Framework 6

Questo articolo correlato getta più luce sulla nuova API

Nota a margine:

Questo pezzo di codice:

 public virtual void SaveAndCommitAsync(TEntity entity) { try { Save(entity); _unitOfWork.SaveChangesAsync(); } catch (DbEntityValidationException e) { } } 

Non sta facendo quello che pensi che stia facendo. Quando si esegue un metodo che è asincrono, si dovrebbe in genere attendere in modo asincrono utilizzando la parola chiave await . Questo metodo:

  1. Sta usando void come tipo di ritorno. Se si tratta di un’API asincrona, deve essere almeno un’attività asincrona. async void metodi async void sono solo per i gestori di eventi , dove questo chiaramente non è il caso qui
  2. L’utente finale probabilmente sarà in attesa su questo metodo, dovrebbe essere trasformato in:

     public virtual Task SaveAndCommitAsync(TEntity entity) { try { Save(entity); return _unitOfWork.SaveChangesAsync(); } catch (DbEntityValidationException e) { } } 

Se si desidera includere un ambito di transazione , è necessario attendere questo metodo:

 public virtual async Task SaveAndCommitAsync(TEntity entity) { try { Save(entity); await _unitOfWork.SaveChangesAsync(); } catch (DbEntityValidationException e) { } } 

Lo stesso vale per il resto dei tuoi metodi asincroni. Una volta che una transazione è lì, assicurati di attendere sul metodo.

Inoltre, non ingoiare eccezioni come queste, fare qualcosa di utile con loro o semplicemente non catturarle.