L’uso di transazioni nell’ EF alcune volte può richiedere una gestione custom. Sebbene nella maggior parte dei casi è il framework stesso a gestire le transazioni per noi ( es. ogni volta che aggiungiamo/ modifichiamo/eliminiamo un’entity tramite il metodo SaveChanges() ), in alcune situazioni particolari si potrebbe richiedere la gestione dello 'scope' transazionale a mano. Una di queste situazioni si presenta ad esempio quando si lavora con ObjectContext multipli: infatti, in casi di questo tipo si può ricorrere all'utilizzo di un "ambiente" esterno all’EF ( es. System.Transactions ) da cui partire anche per implementare una propria strategia di recovery.
Entriamo nel merito:
- Se invochiamo SaveChanges() o SaveChanges(true), l’EF applica i cambiamenti e scarta le informazioni di tracking. Se qualcosa va storto da qualche parte nella nostra transazione, non siamo più in grado di recuperare quanto effettuato mediante il nostro ObjectContext.
- Se invece usiamo SaveChanges(false) in combinazione con il metodo AcceptAllChanges() abbiamo tra le mani qualcosa di più consistente. Infatti, tramite SaveChanges(false) diciamo all’EF non solo di eseguire gli statement previsti, ma anche di “ricordare” i cambiamenti apportati, in modo tale che essi possano essere eventualmente rieseguiti in un secondo momento se necessario. Ovvero, se la nostra transazione fallisse, niente ci vieterebbe di invocare nuovamente SaveChanges(false) per ritentare. Inoltre possiamo sempre investigare all'interno del solito ObjectStateManager per avere il log di cosa è successo. Infine, quando siamo sicuri che tutto è andato come previsto, con il metodo AcceptAllChanges() imponiamo che le informazioni di tracking vengano definitivamente scartate.
Tornando al nostro scenario, nel caso di transazioni che interessano ObjectContext multipli potremmo scrivere qualcosa del genere:
using (TransactionScope scope = new TransactionScope())
{
try
{
// Save changes but NOT reset tracking info
contextA.SaveChanges(false);
contextB.SaveChanges(false);
// Other logic...
}
catch (Exception ex)
{
// We can retry/recover here
// by using tracking info
}
// Everything is complete
scope.Complete();
// Reset tracking info
contextA.AcceptAllChanges();
contextB.AcceptAllChanges();
// We can't retry/recover here
// by using tracking info
}