Το Repository Pattern είναι ένα από τα πιο γνωστά design patterns στην ανάπτυξη λογισμικού. Σκοπός του είναι να απομονώνει τη λογική πρόσβασης στα δεδομένα από την υπόλοιπη εφαρμογή, παρέχοντας ένα καθαρό και επαναχρησιμοποιήσιμο interface για CRUD (Create, Read, Update, Delete) λειτουργίες. Στην περίπτωση ενός generic repository, το pattern επεκτείνεται ώστε να μπορεί να χειρίζεται οποιονδήποτε τύπο entity, χωρίς να χρειάζεται να γράφουμε ξεχωριστό repository για κάθε μοντέλο.
Στο πλαίσιο του Entity Framework Core, ο ρόλος του repository είναι να αλληλεπιδρά με το DbContext, το οποίο αντιπροσωπεύει την εφαρμογή της βάσης δεδομένων, διαχειρίζεται τα entities και παρακολουθεί τις αλλαγές τους. Η βασική αποστολή του repository είναι να κάνει το mapping μεταξύ των entity objects και των πινάκων της βάσης, ενώ παράλληλα αποφασίζει πότε οι λειτουργίες θα εκτελούνται σύγχρονα (synchronous) και πότε ασύγχρονα (asynchronous), ανάλογα με το αν υπάρχει πραγματικό input/output.
Η σημασία του input/output και η σχέση του με async/await
Στον κόσμο των εφαρμογών .NET, η έννοια του input/output (input/output) αφορά κάθε λειτουργία που απαιτεί επικοινωνία έξω από τη μνήμη της εφαρμογής. Παραδείγματα περιλαμβάνουν:
- Queries σε βάσεις δεδομένων
- Αποθήκευση ή ανάγνωση αρχείων
- Κλήσεις προς εξωτερικά APIs ή υπηρεσίες δικτύου
Οι λειτουργίες αυτές δεν ολοκληρώνονται άμεσα, αλλά απαιτούν χρόνο αναμονής μέχρι να επιστρέψει η απάντηση από τον εξωτερικό πόρο. Αν η μέθοδος εκτελείται με σύγχρονο τρόπο, το thread που εκτελεί την κλήση παραμένει μπλοκαρισμένο μέχρι να ολοκληρωθεί η διαδικασία, γεγονός που περιορίζει την κλιμάκωση και την ανταπόκριση της εφαρμογής. Αντίθετα, η χρήση ασύγχρονων μεθόδων επιτρέπει στο thread να απελευθερώνεται προσωρινά, ώστε να μπορεί να εξυπηρετήσει άλλα requests, ενώ η λειτουργία συνεχίζεται στο background μέχρι να ολοκληρωθεί το input/output.
Η σωστή χρήση της ασύγχρονης εκτέλεσης είναι κρίσιμη σε περιβάλλοντα web, όπως το ASP.NET Core, όπου ο αριθμός των διαθέσιμων threads είναι περιορισμένος. Σε περιπτώσεις όπου το repository δεν πραγματοποιεί πραγματικό input/output, η χρήση async είναι περιττή και μπορεί να προκαλέσει περιττό overhead.
Σύγχρονες λειτουργίες: Add, Update, Delete
Οι μέθοδοι Add, Update και Delete του repository ασχολούνται αποκλειστικά με την διαχείριση του state των entities μέσα στο DbContext. Για παράδειγμα, η Add απλά θέτει το entity σε κατάσταση Added, η Update το θέτει σε Modified, και η Delete το θέτει σε Deleted. Κανένα από αυτά τα βήματα δεν πηγαίνει πραγματικά στη βάση δεδομένων ούτε απαιτεί επικοινωνία με εξωτερικό πόρο.
public T Add(T entity)
{
_context.Add(entity); // αλλάζει μόνο το state στο Change Tracker
return entity;
}
Αυτό σημαίνει ότι αυτές οι λειτουργίες είναι απολύτως σύγχρονες και μπορούν να εκτελούνται άμεσα, χωρίς την ανάγκη ασύγχρονου wrapper. Η πραγματική επικοινωνία με τη βάση, όπου πραγματοποιείται input/output, συμβαίνει μόνο κατά την κλήση του SaveChangesAsync().
Η επιλογή να κρατήσουμε αυτές τις μεθόδους σύγχρονες παρέχει καθαρότητα και μειώνει την πολυπλοκότητα του κώδικα, ενώ διατηρεί πλήρη συνέπεια στο σχεδιασμό. Αν αντί για αυτό τυλίξουμε τις μεθόδους σε Task.FromResult, θα δημιουργήσουμε ένα “fake async”, δηλαδή ένα task που φαίνεται ασύγχρονο αλλά στην πραγματικότητα εκτελείται άμεσα και δεν προσφέρει κανένα πλεονέκτημα σε επίπεδο scalability.
Ασύγχρονες λειτουργίες: GetAll και GetByIdAsync
Αντίθετα, οι μέθοδοι GetAll και GetByIdAsync κάνουν πραγματικό input/output στη βάση δεδομένων. Στην περίπτωση του GetAll, η μέθοδος στέλνει ένα query μέσω της κλήσης ToListAsync(), το οποίο αναμένει την επιστροφή όλων των εγγραφών ενός πίνακα:
public async Task<IEnumerable<T>> GetAll()
{
return await _context.Set<T>().ToListAsync();
}
Η μέθοδος FindAsync ή FirstOrDefaultAsync στον GetByIdAsync λειτουργεί ανάλογα, αναζητώντας συγκεκριμένο entity με βάση το κλειδί του. Και στις δύο περιπτώσεις, η εκτέλεση απαιτεί πραγματικό χρόνο input/output, καθώς η βάση πρέπει να επεξεργαστεί το query και να επιστρέψει τα αποτελέσματα. Χρησιμοποιώντας async/await, η κλήση δεν μπλοκάρει το thread, επιτρέποντας στην εφαρμογή να συνεχίσει άλλες εργασίες μέχρι να ολοκληρωθεί το query.
Ο ρόλος του DbContext και του Change Tracker
Ο DbContext διατηρεί ένα Change Tracker, που παρακολουθεί όλες τις αλλαγές στα entities. Όταν καλείται μια μέθοδος όπως Add, το entity προστίθεται στον tracker, αλλά δεν γράφεται αμέσως στη βάση. Αυτό επιτρέπει:
- Συγκέντρωση πολλών αλλαγών και εκτέλεση τους σε μία κλήση SaveChangesAsync().
- Αποφυγή περιττών κλήσεων input/output.
- Καλύτερη απόδοση και διαχείριση transaction.
Επομένως, το repository διαχωρίζει καθαρά τις λειτουργίες:
- Αλλαγή state in-memory → σύγχρονες μέθοδοι (Add/Update/Delete)
- Πραγματικό input/output → ασύγχρονες μέθοδοι (GetAll/GetByIdAsync/SaveChangesAsync)
Αυτός ο διαχωρισμός αποτελεί best practice, εξασφαλίζοντας καθαρότητα, αναγνωσιμότητα και βελτιστοποίηση πόρων.
Async vs Sync: Πρακτικές συστάσεις
Async: Χρησιμοποιείται μόνο όταν υπάρχει πραγματικό input/output ή μακροχρόνια λειτουργία που μπλοκάρει το thread. Παρέχει scalability και responsiveness σε web εφαρμογές.
Sync: Χρησιμοποιείται όταν η λειτουργία εκτελείται άμεσα στη μνήμη και δεν απαιτεί αναμονή για εξωτερικό πόρο. Απλοποιεί τον κώδικα και αποφεύγει περιττό overhead.
Το async δεν σημαίνει “τρέχει σε άλλο thread”. Σημαίνει ότι η μέθοδος επιστρέφει τον έλεγχο στο thread μέχρι να ολοκληρωθεί το input/output.
Συμπέρασμα
Η σωστή χρήση σύγχρονων και ασύγχρονων μεθόδων σε ένα generic repository βασίζεται στο αν μια λειτουργία απαιτεί πραγματικό input/output. Οι μέθοδοι που διαχειρίζονται μόνο το state των entities παραμένουν σύγχρονες, ενώ οι μέθοδοι που αλληλεπιδρούν με τη βάση δεδομένων πρέπει να είναι ασύγχρονες, χρησιμοποιώντας async/await.
Αυτός ο σχεδιασμός:
- Προστατεύει τα threads του web server
- Κάνει το repository καθαρό, επαναχρησιμοποιήσιμο και επεκτάσιμο
- Αποσαφηνίζει πότε χρησιμοποιούμε async και γιατί
- Ενισχύει τη διαχείριση των input/output operations με ασφάλεια και συνέπεια
Με λίγα λόγια, η κατανόηση του διαχωρισμού in-memory state vs database input/output είναι κρίσιμη για κάθε developer που θέλει να γράφει καθαρό, scalable και maintainable code με Entity Framework Core.

Top comments (0)