Om du ger dig in i .NET-världen och börjar läsa tekniska manualer eller böcker om webb-API:er, kommer du förmodligen att stöta på repository-mönstret . Till en början kan det verka som att vi komplicerar saker och ting genom att lägga till extra lager, men det är faktiskt en strategi för att förhindra att koden blir en röra som är svår att underhålla i längden.
I grund och botten pratar vi om att placera en mellanhand mellan din applikations domän och databasen. Istället för att dina controllers behöver veta exakt hur man gör en SQL-fråga eller hur Entity Framework fungerar, begär de helt enkelt data från ett objekt som vet hur man hämtar den, oavsett vilken intern metod det använder.
Vad exakt består detta mönster av?
Föreställ dig arkivet som en svart låda . Du begär en användare med deras ID, och den returnerar det till dig. Det spelar ingen roll om användaren kommer från en MySQL-databas, en JSON-fil eller till och med ett externt API. Denna abstraktion av datalagret gör att din applikations kärna kan vara lagringsagnostisk.
I C#-ekosystemet uppnås detta genom att implementera gränssnitt . Vi definierar de åtgärder vi vill utföra (som Infoga, Ta bort eller Sök) i ett gränssnitt och skapar sedan en klass som implementerar det gränssnittet med den faktiska Entity Framework-logiken. På så sätt känner resten av systemet bara till gränssnittet, inte den konkreta implementeringen.
Tvingande skäl att genomföra det
Även om vissa kanske kallar det ett föråldrat koncept, förblir det grundläggande av flera anledningar. För det första finns det enkla underhållet . Om du någonsin tvingas byta databaser (något som inte händer ofta, men det kan), behöver du inte skriva om all affärslogik, bara repository-klassen.
En annan viktig punkt är enhetstestning . Om din affärslogik är kopplad till DBContext är det besvärligt att testa den eftersom du behöver en riktig, fungerande databas. Genom att använda repositories kan du skapa en mock eller simulator som returnerar dummydata i minnet, vilket gör att du kan validera din kod på millisekunder utan att röra databasen.
Debatten om entitetsramverket
Många undrar om det här mönstret verkligen är nödvändigt när vi redan använder DbContext och DbSet , eftersom Entity Framework tekniskt sett redan implementerar en sorts arbetsenhet och repository. Svaret är att det beror på dina behov, men att lägga till ett extra lager hjälper till att frikoppla koden från ramverket.
Om ditt projekt är en enkel CRUD-applikation kan detta vara en onödig omkostnad. I företagsprojekt där en ren arkitektur önskas (som Hexagonal- eller Onion-arkitekturer) är det dock enda sättet att garantera att appen är skalbar och flexibel att separera domänen från infrastrukturen.
Det generiska arkivet och arbetsenheten
För att undvika att skriva samma insert- och delete-kod för varje entitet (användare, produkter, ordrar etc.) används ofta en IGenericRepository<T> . Denna använder generiska typer för att tillhandahålla grundläggande operationer till alla klasser, vilket drastiskt minskar kodredundansen.
Det är här Unit of Work kommer in i bilden . När du har flera arkiv som arbetar samtidigt riskerar du att vart och ett öppnar sin egen anslutning till databasen. Unit of Work centraliserar detta och säkerställer att alla arkiv delar samma kontext . Detta är avgörande för att hantera transaktioner: antingen sparas alla ändringar från alla arkiv, eller så sparas inga om ett fel uppstår.
Praktisk implementering i utvecklingsflödet
För att implementera detta definierar vi först gränssnittet med nödvändiga metoder. Sedan injicerar repository-klassen DBContext för att köra frågorna. Slutligen, i kontrollenheten, injicerar vi inte kontexten direkt, utan snarare repository-gränssnittet eller Unit of Work.
Genom att göra detta blir regulatorn mycket lättare. Istället för att hantera komplexa filter och kopplingar I SQL anropar man helt enkelt en metod som GetByIdOm vi behöver filtrera data effektivt kan vi skicka lambda-uttryck till det generiska arkivet så att databasen gör det tunga arbetet och inte webbservern, och därmed undviker prestandaproblem med stora datamängder.
Denna metod skyddar applikationens kärna från externa förändringar och gör att utvecklingen kan fokusera på affärsregler. Genom att integrera komponentavkoppling genom beroendeinjektion uppnår vi ett system där varje del är oberoende, enkel att ersätta och framför allt mycket mer robust mot vanliga persistensfel. Dela denna information så att fler användare kan lära sig om den.
