Objektno-relacijsko preslikavanje s Entity Frameworkom
Autor: Denis Balant, Enej Hudobreznik
Unatoč brzom razvoju tehnologije, glavni jezik za upravljanje relacijskim bazama podataka i dalje je SQL ili strukturirani upitni jezik, koji uz brojne prilagođene izvedenice (PostgreSQL, MySQL...) vuče korijene iz 70-ih godina prošlog stoljeća.
Pisanje SQL upita često se pokaže dosta dugotrajnim poslom, posebno sa složenijim strukturama baza podataka. Umjesto ručnog pisanja upita i pretvorbi u objekte, tipičnih za objektno orijentirane programske jezike, mnogo je prirodnije i često jednostavnije mapirati klase iz izvornog koda u shemu podataka, a ne obrnuto. Time se štedi vrijeme razvoja, a ujedno je puno lakše prepoznati odnose među entitetima. Ova metoda se naziva objektno-relacijsko preslikavanje (ORM).
Za .NET okruženje, Microsoft u tu svrhu nudi Entity Framework Core (skraćeno EF Core) okvir otvorenog koda, koji omogućuje razvoj orijentiran na podatke (pristup Code First), gdje se međusobno povezani podaci jednostavno prikupljaju u klasama koje predstavljaju tablice, a okvir se temelji na njihovoj osnovi, razumije odnose i stvara shemu baze podataka u vlastitom formatu. Najveća prednost ovakvog pristupa je mogućnost instaliranja podatkovne sheme na željenu bazu podataka bez poznavanja SQL-a.
EF Core komunicira s bazama podataka putem biblioteka dodataka koje se nazivaju dobavljači baze podataka, tako da je prebacivanje na drugu bazu podataka jednostavno. Korisnik ih može instalirati putem upravitelja paketa (NuGet za C#). Službene biblioteke dostupne su samo za Microsoftova rješenja SQL Server i Azure Cosmos DB te projekt SQLite, a zahvaljujući snažnoj zajednici otvorenog koda okvir podržava praktički sve glavne relacijske baze podataka (MySQL, PostgreSQL...).
Kao primjer modeliranja podataka pogledajmo tablicu "Student" koja ima atribute ime (niz do dvadeset znakova), prezime (niz do dvadeset znakova) i jedinstveni identifikator (cijeli broj).
U PostgreSQL-u ovu tablicu definiramo na sljedeći način:
Uz definicije osnovnih tipova za ime i prezime, također zahtijevamo da nikada nisu prazni (NOT NULL). Tip SERIAL predstavlja jedinstveni cijeli broj, koji u našem slučaju služi kao primarni ključ ID-a. Za svaki dodani entitet automatski se određuje na temelju ID-a prethodno dodanog identiteta koji se jednostavno povećava.
Međutim, puno je lakše definirati tablicu kao klasu u izvornom kodu logike aplikacije pomoću ORM-a koji sam stvara objekt i odgovarajuće zahtjeve baze podataka. Oznaka Key iznad atributa klase označava primarni ključ, a Required označava da atribut ne smije biti prazan.
Nakon razvoja, okvir EF Core omogućuje nam da automatski generiramo shemu podataka iz koda i implementiramo je u odabranu bazu podataka putem alata naredbenog retka koji su uključeni u okvir. Kroz ove migracije upravlja se postupnim promjenama sheme baze podataka, koje osiguravaju da baza podataka ostane sinkronizirana s podatkovnim modelom aplikacije. Nove migracije, koje su pohranjene u obliku posebnih klasa u EF Core, kreiraju se usporedbom trenutnog podatkovnog modela s trenutnom shemom baze podataka (stanje zadnje migracije).
Veza s bazom podataka apstrahirana je klasom koja nasljeđuje od klase DbContext. Njegovi atributi su zbirke entiteta tipa DbSet koji se mapiraju u tablice.
Navedeni okvir za pisanje upita koristi sintaksu LINQ, koja predstavlja objedinjeni način dohvaćanja i obrade podataka iz različitih izvora. Upit se zatim prevodi u SQL, a sam rezultat se prevodi natrag u objekt, njegov atribut ili tablicu objekata.
Primjer u nastavku prikazuje upit za studenta s poznatim upisnim brojem. Dovoljan je samo jedan poziv metode Find s vrijednošću primarnog ključa (ID).
Ako želimo postići isti rezultat bez korištenja ORM alata, potrebno je znatno više koda. Prikazan je primjer korištenja biblioteke NpgSql. Prvo moramo kreirati objekte koji predstavljaju SQL upit i čitati bazu podataka, pri čemu moramo paziti da ispravno uključimo parametar kako bismo izbjegli moguće ranjivosti, kao što je npr. SQL injekcija. Ovaj put moramo sami kreirati studentski objekt, ali moramo paziti na redoslijed atributa u upitu i mogućnost greške u upitu.
Objektno-relacijsko preslikavanje nam tako nudi sloj apstrakcije koji značajno ubrzava razvoj softvera zbog preslikavanja iz objektno orijentiranog razvojnog plana, jer SQL kod ne treba pisati odvojeno od izvornog (često objektno orijentiranog) koda, dobro napisan ORM podržava dobre razvojne obrasce i prakse za dizajn aplikacija, dok omogućuje ne-SQL programerima da lakše integriraju relacijsku bazu podataka u svoju aplikaciju.
Međutim, važno je naglasiti da ORM nije savršeno rješenje. Slabost je upravo u apstrakciji koju nam nudi. Generira značajno više SQL koda nego što bi programer napisao, što može uvelike utjecati na brzinu aplikacije, može sakriti loše prakse pristupa bazi podataka i predstavlja problem kompatibilnosti s prethodnim verzijama. Iako je generirani kod u većini slučajeva ispravan, preporuča se da ga provjerite i testirate ručno.
Objektno relacijsko preslikavanje je funkcionalnost koja nije izvorna za .NET okruženje. Funkcionalnost pruža većina okvira i biblioteka za različite jezike. Primjeri su Django za Python, Gorm za Go, Spring za Javu, Prisma za JavaScript (ili Node.js)...