Έστω ότι μια διαδικασία αποθηκεύει πρώτα παραγγελία και μετά τιμολόγιο.

Θέλουμε να αποθηκευτούν και τα δύο. Αν αποτύχει κάποιο βήμα, θέλουμε να ακυρωθούν οι αλλαγές της διαδικασίας.

Χρησιμοποιούμε τρεις βασικές μεθόδους:

Μέθοδος Σημασία
BeginTransactionAsync() Ξεκίνα τη συναλλαγή.
CommitAsync() Οριστικοποίησε τις αλλαγές της.
RollbackAsync() Ακύρωσε τις αλλαγές της.

Παράδειγμα

Το db είναι το αντικείμενο πρόσβασης στη βάση μέσω EF Core.

await using var transaction = await db.Database.BeginTransactionAsync(); var order = new Order { ProductId = 7, Quantity = 2 }; db.Orders.Add(order); await db.SaveChangesAsync(); db.Invoices.Add(new Invoice { OrderId = order.Id }); await db.SaveChangesAsync(); await transaction.CommitAsync(); 

Η πρώτη αποθήκευση γράφει την παραγγελία και μας δίνει το ID της.

Η δεύτερη γράφει το τιμολόγιο.

Το CommitAsync() οριστικοποιεί τη συναλλαγή.

Το await using εξασφαλίζει τον καθαρισμό της όταν φύγουμε από αυτό το τμήμα κώδικα. Αν η δεύτερη αποθήκευση αποτύχει πριν φτάσουμε στο commit, η ενεργή συναλλαγή ακυρώνεται κατά την αποδέσμευσή της.

Το SaveChanges δεν είναι το ίδιο με το Commit

Μέσα σε δικό μας transaction:

  • SaveChangesAsync() σημαίνει «στείλε τις αλλαγές στη βάση».
  • CommitAsync() σημαίνει «οριστικοποίησε τη συναλλαγή».

Το commit δεν αποθηκεύει μόνο του αλλαγές που υπάρχουν ακόμη αποκλειστικά στη μνήμη. Τεκμηρίωση Microsoft

Ακύρωση της συναλλαγής με RollbackAsync()

Το RollbackAsync() ζητά να ακυρωθούν οι αλλαγές μιας συναλλαγής που δεν έχει οριστικοποιηθεί με επιτυχημένο commit.

Στο παράδειγμα θέλουμε να αποθηκεύσουμε παραγγελία και τιμολόγιο μαζί. Αν αποτύχει η αποθήκευση του τιμολογίου, ακυρώνουμε και την παραγγελία.

await using var transaction = await db.Database.BeginTransactionAsync(); try { var order = new Order { ProductId = 7, Quantity = 2 }; db.Orders.Add(order); await db.SaveChangesAsync(); db.Invoices.Add(new Invoice { OrderId = order.Id }); await db.SaveChangesAsync(); // Τα δύο βήματα πέτυχαν: οριστικοποιούμε. await transaction.CommitAsync(); } catch { // Ζητάμε ακύρωση αμέσως. await transaction.RollbackAsync(); // Προωθούμε το σφάλμα για περαιτέρω χειρισμό. throw; } 

Το πρώτο SaveChangesAsync() γράφει την παραγγελία στη βάση, αλλά η συναλλαγή παραμένει ανοιχτή.

Αν αποτύχει η δεύτερη αποθήκευση, περνάμε στο catch και ζητάμε ακύρωση με RollbackAsync(). Αν ολοκληρωθούν όλα, κάνουμε commit.

Είναι υποχρεωτικό το RollbackAsync() εδώ;

Όχι, εφόσον το await using καθαρίσει τη συναλλαγή και δεν έχει γίνει commit. Η ενεργή συναλλαγή ακυρώνεται τότε αυτόματα.

Άρα, αν αφαιρέσουμε το RollbackAsync() και το σφάλμα μάς βγάλει από το μπλοκ που περιέχει τη συναλλαγή, ο αυτόματος καθαρισμός αναλαμβάνει την ακύρωση. Δεν χρειάζεται επιπλέον finally για τον καθαρισμό.

Η αυτόματη ακύρωση γίνεται κατά τον καθαρισμό, όχι απλώς επειδή μπήκαμε στο catch.

Πότε καλούμε εμείς το RollbackAsync();

Το χρησιμοποιούμε όταν θέλουμε να ζητήσουμε ακύρωση εκείνη τη στιγμή, πριν βγούμε από το μπλοκ. Για παράδειγμα:

  • Στο catch, για άμεση ακύρωση μετά από αποτυχία.
  • Όταν ένας κανόνας απαιτεί ακύρωση, ακόμη κι αν δεν προέκυψε σφάλμα.

Στο συγκεκριμένο παράδειγμα το καλούμε για να φαίνεται καθαρά η ενέργεια ακύρωσης, παρότι το await using παρέχει και τον αυτόματο καθαρισμό.

Το rollback αφορά τη βάση δεδομένων. Δεν επαναφέρει αυτόματα τις τιμές των C# αντικειμένων και δεν αναιρεί εξωτερικές ενέργειες, όπως αποστολή email.

Μετά από επιτυχημένο commit, δεν μπορεί πλέον να ακυρώσει εκείνη τη συναλλαγή.

nikosstit@gmail.com