Kihagyás

Entity Framework

Az Entity Framework (EF) egy objektumrelációs leképzést (object-relational mapper - ORM) biztosító keretrendszer, mely lehetővé teszi adatbázisokkal történő kommunikációt .NET-es objektumok segítségével. Segítségével kiküszöbölhető az SQL lekérdezések beágyazása a kódba. A gyakorlaton az Entity Framework 6 verziószámú NuGet csomagot használjuk, mert a kabinetes gépeken nem található még meg a .NET Core 3.0.

Adatok lekérdezése és módosítása beágyazott SQL parancsok létrehozása, felparaméterezése és futtatása helyett LINQ kifejezések segítségével valósul meg. Az adatbázisban tárolt entitások megfeleltethetők C# osztályoknak, ahol az adatbázis tábla oszlopai megegyeznek az osztály propertyjeivel. A keretrendszer rendelkezik egy kontextussal, ahol meg kell adni az adatbázisban tárolt entitásoknak megfelelő osztályokat, illetve egy elérési útvonalat.

Code First megközelítés

A megközelítés lényege, hogy először az osztályok kerülnek létrehozásra, majd a keretrendszer létrehozza ezek alapján az SQL táblákat. A kód változását úgynevezett migrációk segítségével lehet követni, ami hasonló egy git commithoz. A modell változása után egy migráció létrehozásával az utolsó pillanatkép óta történt változásokat menti le (pl. átnevezés, új property hozzáadása, törlése, stb).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public override void Up() {
  CreateTable("dbo.Locations",
    c => new {
      ID = c.Long(nullable: false, identity: true),
      UserId = c.String(nullable: false, maxLength: 128),
      SaveTime = c.DateTime(nullable: false),
      DetectionTime = c.DateTime(nullable: false),
      Latitude = c.Double(nullable: false),
      Longitude = c.Double(nullable: false),
      Accuary = c.Single(nullable: false),
      Error = c.Boolean(nullable: false) })
    .PrimaryKey(t => t.ID)
    .ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true)
    .Index(t => t.UserId);
}

Az Up metódusban található kódrészlet fut le a migráció alkalmazásánál, jelen esetben létrehozza a Locations táblát, beállítja az elsődleges és külső kulcsot, majd az indexet. Amennyiben vissza szeretnénk állni az eggyel korábbi pillanatképre, akkor ezeket a módosításokat vissza kell vonni. Ez található a Down metódusban.

1
2
3
4
5
public override void Down() {
  DropForeignKey("dbo.Locations", "UserId", "dbo.AspNetUsers");
  DropIndex("dbo.Locations", new[] { "UserId" });
  DropTable("dbo.Locations");
}

Először a külső kulcs kerül törlésre, majd az index és végül a tábla maga. Más sorrend esetében (pl. tábla először) hiba keletkezne, hiszen a kulcs és az index hivatkozza a táblát, amit így nem lehetne törölni.

Az EF6 ezt a megközelítést nem támogatja SQLite adatbázissal, így a gyakorlaton (2019) nincs használva, viszont hasznos tudás a későbbiekben.

Model First megközelítés

Ebben az esetben már egy meglévő adatbázishoz kell csatlakozni és felhasználni az ott definiált adatstruktúrákat. A kód írásakor figyelni kell arra, hogy az adatbázisban definiált tábla és oszlopneveknek megfelelően legyenek elnevezve, mint az osztályok és propertyjeik.

Heroes alkalmazás kiegészítése EF-el

Telepítés

A projekt megnyitása után a menüből elérhető a NuGet csomagkezelő: Tools -> NuGet Package Manager -> Package Manager Console. Ezután az ablak alján megjelenik a konzol.

1
2
    Install-Package EntityFramework
    Install-Package System.Data.SQLite

Az első parancs feltelepíti az EF-et, a második pedig az SQLite adabázis kiegészítőit, hogy az EF tudjon SQLite adatbázishoz kapcsolódni.

Konfigurálás

A csomagok feltelepülése után az App.config tartalma automatikusa bővül, viszont kézzel is szerkeszteni kell. Először egy új provider-t kell hozzáadni, másodszor pedig egy connectionString-et.

  1. Providerek találhatók az <entityFramework> XML tag alatt, az utolsó a következő:
1
2
3
<provider invariantName="System.Data.SQLite.EF6"
    type="System.Data.SQLite.EF6.SQLiteProviderServices,
        System.Data.SQLite.EF6" />

Ezt a sort meg kell duplázni és módosítani az új sort:

1
invariantName="System.Data.SQLite"
1
2
3
<provider invariantName="System.Data.SQLite"
    type="System.Data.SQLite.EF6.SQLiteProviderServices,
        System.Data.SQLite.EF6" />
  1. Az adatbázis elérési útvonalát is meg kell adni (a gyakorlaton relatív útvonalat).
1
2
3
4
5
<connectionStrings>
  <add name="EFContext"
      connectionString="Data Source=..\..\..\DB\heroes.db"
      providerName="System.Data.SQLite" />
</connectionStrings>

Több is megadható, a névnek egyedinek kell lennie. Maga a connectionString ismerős az ADO.NET óráról, itt nem kell @-ot tenni elé. A végén a providerName megegyezik az 1. pontban hozzáadottal.

Modell

Az adatbázist az alábbi szkripttel lehet létrehozni:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
CREATE TABLE Heroes(
 ID integer NOT NULL PRIMARY KEY AUTOINCREMENT,
 Name text NOT NULL,
 HeroName text NOT NULL,
 Power text NOT NULL,
 Age integer NOT NULL,
 UNIQUE(HeroName)
);

CREATE TABLE Items(
 ID integer NOT NULL PRIMARY KEY AUTOINCREMENT,
 Name text NOT NULL,
 HeroId integer NOT NULL,
 FOREIGN KEY (HeroId)
 REFERENCES Heroes (ID)
);

A már meglévő modell osztályokat nem kell változtatni ahhoz, hogy működjön a keretrendszer.

DAO

Ahhoz, hogy az EF-en keresztül használni lehessen az adatbázist, egy úgynevezett kontextust kell létrehozni (pl. a DAO mappába).

1
2
3
4
5
6
class EFContext : DbContext {
    public EFContext() : base("EFContext") { }

    public DbSet<Hero> Heroes { get; set; }
    public DbSet<Item> Items { get; set; }
}

Az elnevezés tetszőleges, de a DbContext-ből kell származnia. Egy üres konstruktort kell létrehozni, ami az ős konstruktorát meghívja a megfelelő connectionString nevével. A zárójelek { } kitétele szükséges.

A két generikus property azt jelzi, hogy az adatbázis mely tábláit használjuk. A DbSet generikus paraméterében várja azt, hogy mely osztályként értelmezze a táblát és a property neve alapján keresi azt CREATE TABLE Heroes <=> Heroes.

Miután ez megvan, egy DAO megvalósítást is létre kell hozni a DAO mappába. A fejlesztőkörnyezet jelezni fogja, hogy a : IHeroesDao-val indikáltuk a megvalósítást, de nem történt meg, majd felajánlja az automatikus generálást.

1
2
3
class HeroesEFDao : IHeroesDao {
  // ...
}

Minden adatbázisművelethez használni kell az előbb létrehozott EFContext-et, méghozzá using segítségével.

Adatbáziskapcsolat ellenőrzése

Letesztelhető, hogy a konfiguráció jó-e a következő kódrészlettel:

1
2
3
using (var db = new EFContext()) {
  bool isOk = db.Database.Exists();
}

A kódrészlet visszaadja, hogy az EF csatlakozni tudott-e a megadott adatbázishoz.

Hozzáadás

1
2
3
4
5
6
7
public bool AddHero(Hero hero) {
  using (var db = new EFContext()) {
      db.Heroes.Add(hero);
      db.SaveChanges();
  }
  return true;
}

A példából ki van hagyva a hibakezelés. Amennyiben valamilyen hiba történt (nem tudott kapcsolódni az adatbázishoz vagy a hozzáadás nem sikeres), akkor kivétel fog dobódni. A kivételben látható, hogy mely megszorítás sérült (pl. két ugyanolyan nevű hős). A hozzáadás csak akkor lesz propagálva az adatbázis felé, amikor a SaveChanges meghívódik.

Listázás

1
2
3
4
5
public IEnumerable<Hero> GetHeroes() {
  using (var db = new EFContext()) {
    return db.Heroes.ToList();
  }
}

Módosítás

A módosítás végrehajtásához három művelet szükséges: elkérni az adatbázistól a módosítani kívánt objektumot, megváltoztatni azt, végül elmenteni a változtatást. Fontos, hogy ez a három művelet ugyanazon kontextusban hajtódjon végre (egy using-on belül).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public bool ModifyHero(Hero hero) {
  using (var db = new EFContext()) {
    var sh = db.Heroes.SingleOrDefault(h => h.ID == hero.ID);
    if (sh != null) {
        sh.Name = hero.Name;
        sh.HeroName = hero.HeroName;
        sh.Power = hero.Power;
        sh.Age = hero.Age;
        db.SaveChanges();
    }
  }
}

Mint a hozzáadás esetén is, kivétel fog dobódni a mentés során, ha valamilyen megszorítást megsért a változtatásunk (pl. ilyen HősNevű hős már van az adatbázisban).

Hivatkozások

https://docs.microsoft.com/en-us/ef/

https://www.tutorialspoint.com/entity_framework/entity_model_first_approach.htm


Utolsó frissítés: 2023-08-07 09:17:05