2. Entity Framework¶
A házi feladat teljesítésével 4 pont és 3 iMsc pont szerezhető.
GitHub Classroom segítségével hozz létre magadnak egy repository-t. A meghívó URL-t Moodle-ben találod. Klónozd le az így elkészült repository-t. Ez tartalmazni fogja a megoldás elvárt szerkezetét. Hozz létre egy megoldas nevű branchet, és arra dolgozz. A feladatok elkészítése után kommitold és pushold a megoldásod.
A megoldáshoz szükséges szoftvereket és eszközöket lásd itt. A feladat MSSQL adatbázist használ.
Entity Framework Core
A feladatban Entity Framework Core-t használunk. A gyakorlaton használt Entity Framework-től eltérően ez egy platformfüggetlen technológia.
Feladat 0: Neptun kód¶
Első lépésként a gyökérben található neptun.txt fájlba írd bele a Neptun kódodat!
Feladat 1: Adatbázis leképzés Code First modellel és lekérdezések (2 pont)¶
Készítsd el az adatbázisunk (egy részének) Entity Framework leképzését Code First megoldással. Az Entity Framework Core csomag már része a kiinduló projektünknek, így rögtön kódolhatunk is. Az adatelérés központi eleme a DbContext. Ez az osztály már létezik ProductDbContext néven.
-
Képezd le a termékeket. Hozz létre egy új osztályt
DbProductnéven az alábbi kóddal. (A Db prefix egyértelművé teszi, hogy az osztály az adatbázis kontextusában értelmezett. Ez a későbbi feladatnál lesz érdekes.) A leképzésnél többnyire hagyatkozzunk a konvenciókra, azaz a property-k nevénél használjuk az adatbázis oszlopok nevét, így automatikus lesz a leképzés.using System.ComponentModel.DataAnnotations.Schema; namespace ef { [Table("Product")] public class DbProduct { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public string Name { get; set; } public double Price { get; set; } public int Stock { get; set; } } }Menj a
ProductDbContextosztályhoz és töröld a kommentet aProductsproperty elől. -
Készíts egy
DbVatosztályt aVATtábla leképzésére azefnévtérbe aDbProduct-hoz hasonlóan. Ne felejtsd el felvenni a DbSet property-t aProductDbContext-beVatnéven. -
Képezd le a Product - VAT kapcsolatot.
A
DbProductosztályba vegyél fel egyDbVattípusúVatnevű get-set property-t, ez lesz a navigation property. Használd aForeignKeyattribútumot a property felett, ami meghatározza a külső kulcs adatbázis mezőjét ("VatID").Vedd fel ennek az egy-több kapcsolatnak a másik oldalát a
DbVatosztályba. Ez aProductsnevű propertySystem.Collections.Generic.Listtípusú legyen. (Lásd a példában is az előbbi linken.)
A teszteléshez találsz unit teszteket a solution-ben. A tesztek kódja ki van kommentezve, mert nem fordul, amíg nem írod meg a fentieket. Jelöld ki a teljes kódot, és használd az Edit / Advanced / Uncomment Selection parancsot. Ezután a teszteket Visual Studio-ban egyszerűen tudod futtatni, de ha mást használsz fejlesztéshez (pl. VS Code és/vagy dotnet cli), akkor is tudsz teszteket futtatni. Az adatbázis eléréséhez a TestConnectionStringHelper segédosztályban módosíthatod a connection stringet.
Tesztek
A tesztek az adatbázis kiinduló állapotát feltételezik. Futtasd le az adatbázis scriptet a kiinduló állapot visszaállításához.
A tesztek kódját NE módosítsd. Ha a teszteléshez szükséges, ideiglenesen beleszerkeszthetsz, de ügyelj rá, hogy az eredeti állapottal kommitold a megoldásod.
Ha a teszt nem fordul
Ha nem fordulna le a teszt kód, lehet, hogy egy-egy property névnek mást használtál. A saját kódodban javítsd a nevet, ne a tesztekben!
OnConfiguring
A DbContext kódjában nincs szükséged connection stringre. A konstruktor intézi a kapcsolat felépítését. Ne írj OnConfiguring függvényt az osztályba!
BEADANDÓ
A módosított C# forráskódot töltsd fel.
Emellett készíts egy képernyőképet Visual Studio-ból (vagy másik, a fejlesztéshez használt eszközből, ami akár dotnet cli is lehet), amelyben a vonatkozó teszteket lefuttattad. Látszódjon a DbContext kódja és a tesztek futásának eredménye! A képet f1.png néven mentsd el és add be a megoldásod részeként!
Ha dotnet test-et használsz a teszt futtatásához, a képernyőképen látszódjon az összes teszt neve. Ehhez használd a -l "console;verbosity=normal" argumentumot a megtalált tesztek listázásához és futtatásához, valahogy így:
dotnet test -l "console;verbosity=normal"
A képernyőképen levő forráskód tekintetében nem szükséges, hogy a végső megoldásban szereplő kód betűről betűre megegyezzen a képen és a feltöltött változatban. Tehát a tesztek sikeres lefutása után elkészített képernyőképet nem szükséges frissíteni, ha a forráskódban kisebb változtatást eszközölsz.
Feladat 2: Repository megvalósítás Entity Framework-kel (2 pont)¶
A pont megszerzésére az első feladat megoldásával együtt van lehetőség.
Az Entity Framework DbContext-je az előzőekben megírt módon nem használható kényelmesen. Például a kapcsolatok betöltését (Include) kézzel kell kezdeményezni, és a leképzett entitások túlságosan kötődnek az adatbázis sémájához. Egy komplex alkalmazás esetében ezért célszerű a DbContext-et a repository minta szerint becsomagolni, és ily módon nyújtani az adatelérési réteget.
Implementáld a ProductRepository osztályt, amely megvalósítja a termékek listázását és beszúrását. Ehhez már rendelkezésre áll egy új, ún. modell osztály, ami a terméket reprezentálja, de közvetlenül tartalmazza az áfa kulcs százalékos értékét is. Ez az osztály az adatbázis adataiból építkezik, de egységbe zárja az adatokat anélkül, hogy az adatbázishoz kellene fordulni a kapcsolódó áfa rekord lekérdezéséhez. Ez a Model.Product nevű osztály, ami tartalmazza a DbProduct leképzett tulajdonságait, de a DbVat-ra mutató navigation property helyett az int típusú áfakulcs (VAT.Percentage) százalékos értékét tartalmazza.
Implementáld a ProductRepository osztály függvényeit.
- A
Listaz összes terméket adja visszaModel.Producttípusra leképezve. - Az
Insertszúrjon be egy új terméket. A kapott ÁFA kulcs értéknek megfelelően keresse ki az adatbázisból a kapcsolódóVATrekordot, vagy ha nem létezik ilyen kulcs még, akkor szúrjon be egy újVATrekordot is! A metódus visszatérési értéke az új elem ID-ja legyen (amit természetesen az adatbázis generál). - A
Deletetörölje a termék rekordot a megadott id alapján. Csak a termék rekordot kell törölni, kapcsolódó sorokat nem. Ha a törlés külső kulcsok miatt nem hajtható végre, engedd a hívót értesülni a hibáról. Ha a termék nem létezik, a függvény hamis visszatérési értékkel jelezze, a sikeres törlést pedig igazzal. - A
ProductRepositoryosztály definícióját (pl. osztály neve, konstruktor, függvények definíciója) ne változtasd meg, csak a függvények törzsét írd meg. - A kódban a
ProductRepository.createDbContext()-et használd a DbContext létrehozásához (és ne aTestConnectionStringHelper-t).
BEADANDÓ
A módosított C# forráskódot töltsd fel.
MÉG NEM VÉGEZTÉL
Ha push-oltad a kódodat, készíts egy PR-t, amihez rendeld hozzá a gyakorlatvezetődet! (részletek: a házi feladat leadása oldalon)
Feladat 3: Logikai törlés Entity Framework-kel (3 iMSc pont)¶
A pont megszerzésére az első két feladat megoldásával együtt van lehetőség.
Az adatbázisból való törlés egy olyan művelet, ami számos nemkívánt hatással rendelkezik. Egy törölt adatot visszaállítani sokkal nehezebb, néha nem is lehetséges következmények nélkül. Az adat törlésével akár a teljes adattörténet elveszhet, nem tudunk a törlés előtti állapotról, különböző statisztikákban nem tudjuk felhasználni. Ráadásul előfordulnak olyan esetek, például amikor olyan más táblákkal való kapcsolatok és külső kulcs kényszerek vannak, hogy a törlés kihatással van azokra a táblákra is.
Ezen problémák áthidalására a leggyakoribb megoldás, hogy egy nem végleges törlést, hanem egy úgynevezett logikai törlést (soft delete) vezetünk be. Ebben az esetben egy mező (tipikusan IsDeleted névvel) átállításával jelezzük, hogy az adott adat már törölve van. Így a rekord megmarad az adatbázisban is, de tudjuk szűrni, hogy töröltek-e.
A szűrés naiv implementációja azonban nem kényelmes. Képzeljük el, hogy minden lekérdezés vagy mentés esetén oda kell írni a kifejezésbe, hogy ne hasson ezekre a törölt elemekre. Ennek érdekében az Entity Framework egyik funkcióját érdemes kihasználni, a Global Query Filter-t. Ennek a segítségével olyan szűrőfeltételeket határozhatunk meg, amiket globálisan, minden egyes lekérdezésnél automatikusan alkalmaz az Entity Framework.
Implementáld a logikai törlést az előbbiekben elkészített DbProduct osztályhoz (több megoldási lehetőség is van, tetszőlegesen választhatod bármelyiket):
Módosíthatóság
Bár az előző feladatban volt megkötés, hogy ne legyen az OnConfiguring függvény felüldefiniálva, amennyiben szükségesnek látod, itt nyugodtan lehet (illetve más függvényeket is felüldefiniálhatsz a DBContext megvalósításban)!
-
Vegyél fel egy
IsDeletedmezőt aProducttáblába és hozzá egy tulajdonságot az entitásba, ami jelzi az alkalmazásunk számára, hogy az adott entitás törölt állapotban van!Adatbázis séma módosítás
Az adatbázis sémáját nem célszerű most EF migrációval módosítani, mert a kiinduló állapotot sem azzal hoztuk létre. Módosítsuk most kézzel SQL-ből vagy az SQL Management Studio felületéről az érintett táblát.
-
Vegyél fel egy QueryFilter-t, ami minden lekérdezéskor kiszűri azokat a termékeket, amiket már töröltünk, így azokat nem kapjuk vissza!
-
Az adatbázisból való törlés viselkedését változtasd meg általánosan a
DbContextmentés műveleteit kibővítve (erre több kiterjesztési pontot is nyújt az EFCore), hogy az igazi törlés helyett csak átváltoztassa azIsDeletedváltozót! Ne változtasd meg a törlés műveletet a repositoryban módosításra!
BEADANDÓ
A módosított C# forráskódot töltsd fel.