I modelli concettuali che possiamo definire tramite l' Entity Framework supportano 3 tipi diversi di ereditarietà:
- Table-per-Hierarchy (TPH): Una tabella del database contiene i dati di tutti i tipi della gerarchia, lasciando ad una colonna il compito di fungere da discriminatore.
- Table-per-Type (TPT): Una tabella contiene le proprietà dell'Entity base. Ciascun sottotipo mappa quindi le sue specifiche proprietà su una tabella distinta.
- Table-per-Concrete Class (TPC): Ad ogni tipo della gerarchia viene assegnata una tabella che ne mappa tutte le proprietà, incluse quelle ereditate.
Di tutti questi modelli di ereditarietà, il più semplice e facile da implementare è sicuramente il Table-per-Hierarchy (conosciuto anche come "Single Table Inheritance"), che tra l'altro è anche supportato da LINQ to SQL, a differenza degli altri due. Come è facile intuire, questo approccio in realtà non è poi così "pulito" :
- Le informazioni relative a tutti i tipi della gerarchia vengono memorizzate in un'unica tabella e per ottenere la flessibilità necessaria si è costretti ad impostare le colonne di ciascun sottotipo come "allow nulls".
- Di conseguenza, poiché la tabella risultante possiederà valori NULL in corrispondenza di colonne non utilizzate nel mapping verso le proprietà di ciascun sottotipo, è possibile che questa soluzione dia vita ad un inutile spreco di spazio su disco (dico "possibile" perché ormai i DB engine moderni gestiscono i valori NULL con tecniche sempre più ottimizzate - es. Sparse Columns in SQL Server 2008).
Ad ogni modo, allontaniamoci un attimo dalle considerazioni di carattere prestazionale e concentriamoci su un semplice esempio di implementazione di uno scenario TPH utilizzando l'EF. Prendiamo come esempio un modello concettuale relativo ad un embrionale RPG (Role-playing game):
| - Le Entities di tipo Warrior, Priest e Wizard sono derivate dell'Entity padre Character e costituiscono le uniche classi concrete del sistema.
- L'Entity padre Character è astratta, ovvero rappresentata da una classe base non istanziabile direttamente. Per soddisfare un requisito del genere, basta impostare a true l'attributo Abstract dell' Entity (si può fare comodamente da design).
|
Nel DB abbiamo invece una singola tabella Characters a cui verrà affidato il compito di memorizzare le informazioni di tutti i tipi della gerarchia. In particolare, la colonna TypeID funge da discriminatore per i vari tipi di Character in quanto conterrà il codice identificativo del CharacterType corrispondente (es. TypeID = 1 -> Warrior).
Ora non rimane altro che impostare i table mappings. Vediamo rispettivamente quelli dell'Entity base Character e di un suo sottotipo (Warrior):
Il menu "Map Entity to functions" è giustamente disabilitato :) | 1. Il model designer si comporta in modo corretto filtrando automaticamente le colonne mappabili in base alla gerarchia di tipi definita nel modello concettuale. 2. La condition "When TypeID = 1" rappresenta il nostro discriminatore per Entità di tipo Warrior. |
N.B.: In questa fase bisogna evidenziare il fatto che, in caso di errori di mappings, il compile-time spesso fornisce messaggi non molto chiari che complicano il troubleshooting specialmente per chi in generale non ha esperienza nel mondo degli ORM.
OK It's code time!
Una volta che la generazione del modello è andata a buon fine, il designer autogenera l'intera infrastruttura per le solite operazioni CRUD a cui possiamo accedere tramite LINQ to Entities. Lo sviluppatore a questo punto può scrivere codice che sfrutta realmente le potenzialità e le best practices architetturali del mondo OO:
using (RPG_Entities rpgEntities = new RPG_Entities())
{
Warrior NewWarrior = new Warrior() { Name = "Ken Shiro" };
Priest NewPriest = new Priest() { Name = "Frate Tac" };
Wizard NewWizard = new Wizard() { Name = "Merlino" };
// Un unico metodo AddToCharacters che gestisce una qualunque sub-Entity della gerarchia
rpgEntities.AddToCharacters(NewWarrior);
rpgEntities.AddToCharacters(NewPriest);
rpgEntities.AddToCharacters(NewWizard);
rpgEntities.SaveChanges();
}
oppure:
using (RPG_Entities rpgEntities = new RPG_Entities())
{
// Fetch in base all' Entity Type
var warriors = rpgEntities.Characters.OfType<Warrior>();
foreach (Warrior warrior in warriors)
{
// Do something...
}
}
Per maggiori informazioni sull' ereditarietà di tipo TPH consiglio la lettura di questi articoli MSDN:
Technorati tags: Entity Framework, Ereditarietà