Η διαχείριση δεδομένων σε εφαρμογές με βάσεις δεδομένων ήταν πάντα μια πρόκληση για τους προγραμματιστές. Πριν την ευρεία υιοθέτηση του Unit of Work, κάθε αλλαγή σε ένα αντικείμενο έπρεπε να εφαρμοστεί ξεχωριστά στη βάση, συχνά με πολλαπλά SaveChanges() calls. Αυτό δημιουργούσε κίνδυνο ασυνέπειας, πολλαπλές κλήσεις στη βάση και σύνθετη λογική rollback.
Η εποχή πριν το Unit of Work
Στις πρώτες εφαρμογές με ORM, η λογική ήταν απλή αλλά επικίνδυνη:
using (var context = new AppDbContext())
{
var product = context.Products.Find(1);
product.Price = 100;
context.SaveChanges(); // Άμεσο commit
var log = new Log { Message = "Αλλάχθηκε τιμή προϊόντος" };
context.Logs.Add(log);
context.SaveChanges(); // Ξεχωριστό commit
}
Εδώ, κάθε call σε SaveChanges() δημιουργούσε ξεχωριστή συναλλαγή. Αν κάτι πήγαινε στραβά μετά την πρώτη αλλαγή, η εφαρμογή βρισκόταν σε κατάσταση ασυνέπειας. Ακόμη και με repositories, το commit ήταν συνήθως ξεχωριστό για κάθε repository, με αποτέλεσμα διάσπαρτη λογική και μεγαλύτερη πιθανότητα σφαλμάτων.
Η γέννηση του Unit of Work
Η ανάγκη ήταν προφανής: οι αλλαγές θα έπρεπε να συγκεντρώνονται σε ένα ενιαίο έργο (unit of work) και να εκτελούνται όλες μαζί ή καμία.
Το Unit of Work λειτουργεί ως διαμεσολαβητής μεταξύ της εφαρμογής και της βάσης δεδομένων. Παρακολουθεί όλα τα αντικείμενα που τροποποιούνται και εφαρμόζει τις αλλαγές με ένα ενιαίο commit, διασφαλίζοντας atomicity και συνέπεια.
Τι κάνει και γιατί είναι σημαντικό
- Παρακολούθηση αλλαγών: γνωρίζει ποια αντικείμενα έχουν τροποποιηθεί.
- Atomic commit: όλες οι αλλαγές εφαρμόζονται ή απορρίπτονται μαζί.
- Μείωση calls στη βάση: αντί για πολλαπλά SaveChanges(), έχουμε ένα μόνο.
- Καθαρός κώδικας: τα repositories δεν χρειάζεται να ξέρουν πότε γίνεται commit.
- Ευκολία στις δοκιμές: η λογική μπορεί να δοκιμαστεί χωρίς άμεση πρόσβαση στη βάση.
Παράδειγμα Unit of Work σε C#
using (var unitOfWork = new UnitOfWork(new AppDbContext()))
{
var product = unitOfWork.Products.GetById(1);
product.Price = 99.99m;
var customer = new Customer { Name = "Γιάννης" };
unitOfWork.Customers.Add(customer);
unitOfWork.Complete(); // Όλες οι αλλαγές γίνονται commit μαζί
}
Αυτό εξασφαλίζει ότι όλες οι αλλαγές εκτελούνται ως ένα ενιαίο έργο, μειώνοντας τα σφάλματα και βελτιώνοντας την απόδοση.
Διαχείριση δύο DbContext
Σε εφαρμογές με δύο βάσεις δεδομένων (π.χ. AppDbContext και LoggingDbContext), το Unit of Work μπορεί να συνδυαστεί με TransactionScope για atomicity:
using System.Transactions;
var appContext = new AppDbContext();
var logContext = new LoggingDbContext();
using (var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
try
{
// Αλλαγές στην κύρια βάση
var product = new Product { Name = "Laptop", Price = 1200 };
appContext.Products.Add(product);
appContext.SaveChanges();
// Αλλαγές στη βάση logs
var log = new Log { Message = $"Προστέθηκε προϊόν {product.Name}" };
logContext.Logs.Add(log);
logContext.SaveChanges();
// Commit και στις δύο βάσεις
scope.Complete();
}
catch (Exception ex)
{
Console.WriteLine("Σφάλμα: " + ex.Message);
// rollback γίνεται αυτόματα
}
}
Σημεία κλειδιά:
- TransactionScope δημιουργεί μια “ambient transaction” για δύο DbContext.
- Τα
SaveChanges()ενημερώνουν τα context, αλλά commit γίνεται μόνο μεscope.Complete(). - Αν υπάρξει σφάλμα, καμία αλλαγή δεν εφαρμόζεται.
Best Practices
- Repositories χωρίς SaveChanges() – commit μόνο από Unit of Work.
- TransactionScope για πολλαπλά DbContext για atomicity.
- Dependency Injection για DbContext για testability και ευελιξία.
- Διαχείριση exceptions γύρω από Complete() για rollback και logging.
- Σαφής διαχωρισμός: Repositories για CRUD, Unit of Work για συναλλαγές.
Συμπέρασμα
Το Unit of Work είναι εργαλείο που φέρνει τάξη, συνέπεια και καθαρότητα στη διαχείριση δεδομένων. Από τα πρώτα χρόνια με αποσπασματικές αλλαγές στη βάση, φτάσαμε σε εφαρμογές όπου όλα τα modifications μπορούν να εκτελούνται ως ένα ενιαίο έργο, ακόμα και με πολλαπλά DbContext.
Συνδυάζοντας Repository Pattern + Unit of Work + TransactionScope, δημιουργούμε εφαρμογές καθαρά δομημένες, ασφαλείς και εύκολα συντηρήσιμες, ιδανικές για σύγχρονες enterprise εφαρμογές.
Βιβλιογραφία
- Fowler, Martin. Patterns of Enterprise Application Architecture. Addison-Wesley, 2003.
- Microsoft Docs. Working with transactions in Entity Framework. Link
- Microsoft Docs. Unit of Work and Repository Patterns in .NET. Link
- Jimmy Bogard. Effective Aggregate Design. 2009.
- Vermeulen, Steven. Entity Framework Core in Action. Manning, 2018.
- Lerman, Julie. Programming Entity Framework: Code First. O’Reilly, 2011.
- Microsoft Docs. TransactionScope class (System.Transactions). Link
Top comments (0)