Prilikom dizajniranja relacijskih baza podataka za različite poslovne svrhe uobičajeno je kreirati tablice za svaku vrstu entiteta koju namjeravamo koristiti i definirati odnose među njima.

database-table

Svako svojstvo entiteta postaje stupac unutar tablice s pripadnim svojstvom klase Entity Framework Core-a. Dodavanje ili promjena svojstava prirodan je dio razvojnog procesa takvih sustava. Ponekad neki entiteti imaju promjenjiv skup generičkih svojstava zbog različitih zahtjeva od strane poslovne domene. Takvi se scenariji često pojavljuju kada entiteti imaju veliki broj mogućih svojstava, pri čemu svaki entitet koristi manji podskup svojstava. Stvaranjem takvog sustava moguće je promijeniti svojstva entiteta bez da se promijeni dizajn baze podataka ili kôd. Kad je takav pristup potreban primjenjuje se implementacija entitetsko-atributno-vrijednosnog modela.

Entitetsko-atributno-vrijednosni model (EAV)

Prilikom dizajniranja baze podataka po entitetsko-atributno-vrijednosnom modelu, bitno je prepoznati koji entiteti trebaju skalabilan skup atributa (svojstava) i stvoriti meta tablice za te atribute. Osnovne tablice su tablice entiteta, atributa i vrijednosti, u najjednostavnijem obliku. Dizajn je moguće proširiti po potrebi.

Entity-Attribute-Value model

Za bolje razumijevanje meta tablica u ovom kontekstu, uzeti ćemo primjer scenarija gdje potrebno opisati različite proizvode (s generičkim svojstvima) i čuvati podatke o artiklima tih vrsta proizvoda u bazi podataka.

Primjer entitetsko-atributno-vrijednosnog (EAV)modela

Generički entitet koji nam je potreban je Product. Umjesto tablice proizvoda sa nepromijenjivim skupom stupaca koja bi predstavljala stvarne artikle, možemo stvoriti tablicu ProductDefinitions. Ova tablica sadrži samo stupce potrebne za definiranje vrste proizvoda, a ne stupce koji definiraju stvarne instance tog proizvoda.

Product-Definitions-table

Zatim nam je potreban skalabilni skup atributa za Product. Takve atribute je moguće dijeliti među različitim proizvodima no radi jednostavnije predodžbe „pretpostavit” ćemo da svaki proizvod ima svoje vlastite atribute. Svaki atribut sadrži fiksne stupce za svojstva koja su potrebna za njegovu definiciju.

tables-with-a-scalable-set-of-attributes

Sada kada imamo tablice koje opisuju „klasu proizvoda“ sa skalabilnim skupom atributa, potrebne su nam tablice za čuvanje stvarnih podataka.

Nazovimo glavnu tablicu tablicom artikala (Items). Ta tablica ne bi trebala sadržavati puno više od Id-a i reference na ProductDefinition, što u osnovi opisuje vrstu odnosa instanca-klasa.

table-describing-a-class-instance-sort-of-a-relationship

Sve ostale vrijednosti svojstava artiklatrebaju postojati kao zasebni objekti, u obliku tablice vrijednosti. I ubrzo pregled nije više tako lijep …

Kad definiramo ProductAttributes, vjerojatno ćemo shvatiti da atribut treba imati tip podataka. Atribut proizvoda bi trebao biti definiran kao cjelobrojna, decimalna, bool, string, datumska ili neka – možda također generička – vrijednost iz definiranog konačnog skupa (definiranog u nekoj drugoj tablici). Za jednostavniji prikaz problematike napravimo tablicu AttributeTypes.

AttributeTypes-table

Sada kada smo definirali tipove atributa, možemo stvoriti tablicu ItemValues (ili tablice) koja će sadržavati vrijednosti za svaki artikal. Postoje različiti pristupi i nijedan od njih nije, nažalost, posebno privlačan:

1. Kreiranje jednog stupca tipa string (varchar) za držanje bilo koje vrste podataka, a zatim spremimo podatke kao JSON ili neki drugi sličan format.

Iako ovaj pristup smanjuje složenost tablice može biti malo zamorno kada se pokušava pristupiti vrijednosti zbog serijalizacije i deserijalizacije podataka u i iz jednog stupca tipa string. Također ne postoji automatsko osiguranje da taj string sadrži vrijednost kako je definirano AttributeType-om i ProductAttribute-om.

table-with-a-single-string-varchar-column

2. Slučaj da je svaki red ItemValuestablice sadrži stupac za svaki tip iz AttributeTypes tablice.

Ovaj pristup omogućuje da stavka iz ProductAttribute-a promijeni svoj AttributeType bez pretjerane muke. A što se tiče nedostataka, prva je očito redundantnost praznih stupaca za tipove podataka koje se ne koriste. Također je vrijedno napomenuti da se provjera jesu li zaista stupci NULL(ukoliko je potrebna) mora provjeriti u kôdu. I na kraju, dodavanje novog AttributeType-a zahtijeva promjene u samoj bazi podataka, čak i ako se takve nadopune vjerojatno neće događati vrlo često.

adding-a-new-AttributeType

3. Izdvajanje zasebne tablice za svaki AttributeType, a zatim kreiranje stupca ‘value’ samo za tip podataka na koju se odnosi svaka tablica.

Prednost je u tome što nemamo prazne stupce. Loša strana je ta što, budući da imamo odvojene tablice, imamo i zasebne kolekcije „vrijednosti“ u EF Core entitetima. Bez obzira, određenim prilagodbama kôda moguće je koristiti sve vrijednosti u istoj kolekciji. Potrebno je i napomenuti da je u slučaju dodavanja novih tablica za novi AttributeType potrebna izmjena kôda.

Make-separate-tables-for-each-AttributeType

Nakon što smo odabrali naš pristup, imamo svoj entitetsko-atributno-vrijednosni (EAV) model u bazi podataka. Može sadržavati Itemsodabrane ProductDefinition. Svaki Itemsadrži skalabilnu kolekciju Valueskoje se odnose na ProductAttribute i imaju AttributeType.

Što sve treba uzeti u obzir kod EAV modela

Prije nego što se odlučite za EAV kao model izbora za vašu aplikaciju, postoje neke stvari koje je potrebno razmotriti.

Performanse

Za razliku od selektiranja određenog broja redaka iz samo jedne tablice, dohvaćanje artikala sa svim njihovim vrijednostima može postati masivan SQL JOIN ako se olako koriste. Include() naredbe, posebno s upitima na kojima se koristi „tracking“. Stvaranje referenci za podređene objekte (Item-> ItemValue-> ProductAttribute) u memoriji može prouzrokovati loše performanse. Bit će potrebno razmotriti što je zapravo potrebno dohvatiti. Dodatno je je potrebno razlučiti je li brže ručno kreirati reference za podređene objekte, umjesto da samo pozovete .Include().ThenInclude().ThenInclude()

manually-create-the-references-for-child-objects

Kompleksnost

Jednostavni zadaci poput filtriranja i grupiranja artikala postaju daleko manje jednostavni kada imamo EAV model. Filtriranje popisa artikala više nije stvar pozivanja jednostavnog .Where() na nekoj kolekciji. Potrebno je pronaći ItemValue po referenci ProductAttribute, uzeti u obzir tip podataka atributa ItemValue, a zatim izvršiti usporedbu same vrijednosti. Time će se u pozadini ponovno stvoriti JOIN-ovi, a ako ne pazimo s dinamički kreiranim upitima (spajanjem izraza .Where()) možemo dobiti horor scenarij.

find-the-ItemValue

Veza generičkih i specifičnih atributa

Sve je to zabavno dok nam ne treba neka određena poslovna logika. Vrlo je vjerojatno da ćemo trebati neke generičke atribute koji su identificirani kao specifični atributi kako bi zadovoljili logiku.

U praksi bi to značilo da će se za izradu nekog posebnog izračuna koristiti generički atribut – Price– i trebat ćemo pronaći način kako doći do tih podataka.
Postavlja se i pitanje samog dizajna, točnije je li bolje dodati atribute kao fiksne stupce u tablici artikala?

fixed-columns-in-the-Item-table

Iako je izvedba jednostavna, takvi atributi nemaju mogućnost interakcije s drugim generičkim atributima, čime postaje nesvrsishodno koristiti EAV model.

Druga mogućnost bi bila dodati svojstvo u ProductAttribute, specificirajući koji sistemski(specifični) tip to svojstvo predstavlja.

add-a-property-to-the-ProductAttribute

Ako koristimo fiksni enum, to je točka u kojoj tablica ProductAttributeprestaje biti općenita, ali relativno je lako prepoznati posebne/sistemske atribute.

manually-create-the-references-for-child-objects

Najbolje rješenje bi bilo kreirati sustav koji koristi reference na strane ključeve kako bi pronašli svojstva s određenim specifičnim identitetima – primjerice tablicu ProductGenericBridge s poljima kao što su ProductAttributeIdUsedAsPrice i ProductAttributeIdUsedAsTax.

Product-Generic-Bridge

In that case, you can keep the EAV tables completely generic, and you can keep the ‘bridge’ completely separate. Changes to the ProductGenericBridge occur only when there are changes to the ‘system properties’ used by the business logic. The generic tables can be completely unaware of that.

Prilikom implementacije takvog sustava veze se moraju postaviti “ručno” za svaki Proizvod koji korisnik stvori. U suprotnom, možda će nedostajati definicija nekih kritičnih svojstava unutar baze podatakta.

Iako EAV model nudi mnogo mogućnosti, potrebno je pripaziti na složenost sustava i implikacije koje se može imati na performanse u ovisnosti o vrsti sustava.

What's your reaction?