Domanda

Esempio

Io ho Person, SpecialPerson, E User. Person E SpecialPerson sono semplicemente persone: non hanno un nome utente o una password su un sito, ma sono archiviati in un database per la tenuta dei registri.L'utente ha tutti gli stessi dati di Person e potenzialmente SpecialPerson, insieme a un nome utente e una password poiché sono registrati al sito.


Come affronteresti questo problema?Avresti un Person tabella che memorizza tutti i dati comuni a una persona e utilizza una chiave per cercare i propri dati SpecialPerson (se è una persona speciale) e Utente (se è un utente) e viceversa?

È stato utile?

Soluzione

Esistono generalmente tre modi per mappare l'ereditarietà degli oggetti sulle tabelle del database.

Puoi creare una grande tabella con tutti i campi di tutti gli oggetti con un campo speciale per il tipo.Questo è veloce ma spreca spazio, sebbene i database moderni risparmino spazio non memorizzando campi vuoti.E se stai cercando solo tutti gli utenti nella tabella, con ogni tipo di persona presente le cose possono rallentare.Non tutti gli or-mapper lo supportano.

Puoi creare tabelle diverse per tutte le diverse classi figlie con tutte le tabelle contenenti i campi della classe base.Questo va bene dal punto di vista delle prestazioni.Ma non dal punto di vista della manutenzione.Ogni volta che la tua classe base cambia, tutte le tabelle cambiano.

Puoi anche creare una tabella per classe come hai suggerito.In questo modo sono necessari i join per ottenere tutti i dati.Quindi è meno performante.Penso che sia la soluzione più pulita.

Quello che vuoi usare dipende ovviamente dalla tua situazione.Nessuna delle soluzioni è perfetta, quindi devi valutare i pro e i contro.

Altri suggerimenti

Dai un'occhiata a Martin Fowler Modelli di architettura delle applicazioni aziendali:

  • Ereditarietà della tabella singola:

    Quando eseguiamo la mappatura su un database relazionale, proviamo a ridurre al minimo i join che possono verificarsi rapidamente durante l'elaborazione di una struttura di ereditarietà in più tabelle.L'ereditarietà della tabella singola mappa tutti i campi di tutte le classi di una struttura di ereditarietà in un'unica tabella.

  • Ereditarietà della tabella delle classi:

    Desideri strutture di database che si associno chiaramente agli oggetti e consentano collegamenti in qualsiasi punto della struttura di ereditarietà.L'ereditarietà della tabella delle classi supporta ciò utilizzando una tabella di database per classe nella struttura di ereditarietà.

  • Eredità della tabella concreta:

    Pensando alle tabelle dal punto di vista dell'istanza di un oggetto, una strada sensata è prendere ogni oggetto in memoria e mapparlo su una singola riga del database.Ciò implica l'ereditarietà della tabella concreta, in cui è presente una tabella per ogni classe concreta nella gerarchia di ereditarietà.

Se Utente, Persona e Persona speciale avessero tutti le stesse chiavi esterne, allora avrei un'unica tabella.Aggiungi una colonna denominata Tipo che è vincolata a Utente, Persona o Persona speciale.Quindi in base al valore di Type si hanno vincoli sulle altre colonne opzionali.

Per il codice oggetto non fa molta differenza se si hanno tabelle separate o più tabelle per rappresentare il polimorfismo.Tuttavia, se devi eseguire SQL sul database, è molto più semplice se il polimorfismo viene catturato in un'unica tabella... a condizione che le chiavi esterne per i sottotipi siano le stesse.

Quello che dirò qui manderà in confusione gli architetti di database, ma ecco qui:

Considera un database visualizzazione come l'equivalente di una definizione di interfaccia.E una tabella è l'equivalente di una classe.

Quindi nel tuo esempio, tutte e 3 le classi di persone implementeranno l'interfaccia IPerson.Quindi hai 3 tabelle, una per ciascuno di "Utente", "Persona" e "Persona speciale".

Quindi disponi di una vista "PersonView" o qualsiasi altra cosa che selezioni le proprietà comuni (come definite dalla tua "interfaccia") da tutte e 3 le tabelle nella vista singola.Utilizza una colonna "PersonType" in questa visualizzazione per memorizzare il tipo effettivo della persona da memorizzare.

Pertanto, quando esegui una query che può essere eseguita su qualsiasi tipo di persona, esegui semplicemente una query sulla vista PersonView.

Questo potrebbe non essere ciò che l'OP intendeva chiedere, ma ho pensato di inserirlo qui.

Recentemente ho avuto un caso unico di polimorfismo db in un progetto.Avevamo dalle 60 alle 120 classi possibili, ciascuna con il proprio set di 30-40 attributi unici e circa 10-12 attributi comuni a tutte le classi.Abbiamo deciso di seguire il percorso SQL-XML e ci siamo ritrovati con un'unica tabella.Qualcosa di simile a :

PERSON (personid,persontype, name,address, phone, XMLOtherProperties)

contenente tutte le proprietà comuni come colonne e quindi un grande contenitore di proprietà XML.Il livello ORM era quindi responsabile della lettura/scrittura delle rispettive proprietà da XMLOtherProperties.Un po 'come :

 public string StrangeProperty
{
get { return XMLPropertyBag["StrangeProperty"];}
set { XMLPropertyBag["StrangeProperty"]= value;}
}

(alla fine abbiamo mappato la colonna xml come un documento Hastable anziché XML, ma puoi utilizzare quello che si adatta meglio al tuo DAL)

Non vincerà alcun premio di design, ma funzionerà se hai un numero elevato (o sconosciuto) di classi possibili.E in SQL2005 puoi ancora utilizzare XPATH nelle tue query SQL per selezionare le righe in base ad alcune proprietà archiviate come XML.è solo una piccola penalizzazione delle prestazioni da accettare.

Esistono tre strategie di base per gestire l'ereditarietà in un database relazionale e una serie di alternative più complesse/su misura a seconda delle specifiche esigenze.

  • Tabella per gerarchia di classi.Una tabella per l'intera gerarchia.
  • Tabella per sottoclasse.Viene creata una tabella separata per ogni sottoclasse con un'associazione 0-1 tra le tabelle sottoclassate.
  • Tabella per classe concreta.Viene creata un'unica tabella per ogni classe concreta.

Ciascuno di questi approcci solleva i propri problemi relativi alla normalizzazione, al codice di accesso ai dati e all'archiviazione dei dati, sebbene la mia preferenza personale sia quella di utilizzare tabella per sottoclasse a meno che non ci sia una prestazione specifica o una ragione strutturale per scegliere una delle alternative.

A rischio di essere un "astronauta dell'architettura", sarei più propenso a utilizzare tabelle separate per le sottoclassi.Fare in modo che anche la chiave primaria delle tabelle delle sottoclassi sia una chiave esterna che rimandi al supertipo.

Il motivo principale per farlo in questo modo è che diventa molto più coerente dal punto di vista logico e non ci si ritrova con molti campi NULL e senza senso per quel particolare record.Questo metodo semplifica inoltre molto l'aggiunta di campi aggiuntivi ai sottotipi durante l'iterazione del processo di progettazione.

Ciò aggiunge lo svantaggio di aggiungere JOIN alle tue query, che può influire sulle prestazioni, ma quasi sempre scelgo prima un design ideale e poi cerco di ottimizzare in seguito se si rivela necessario.Le poche volte che ho seguito la strada "ottimale" prima me ne sono quasi sempre pentito in seguito.

Quindi il mio design sarebbe qualcosa del genere

PERSONA (personid, nome, indirizzo, telefono, ...)

SPECIALPERSON (personid REFERENCES PERSON(personid), campi extra...)

UTENTE (idpersona REFERENCES PERSON(idpersona), nome utente, password crittografata, campi aggiuntivi...)

In seguito potresti anche creare VIEW che aggreghino il supertipo e il sottotipo, se necessario.

L'unico difetto di questo approccio è se ti ritrovi a cercare pesantemente i sottotipi associati a un particolare supertipo.Non c'è una risposta semplice a questa domanda, potresti tenerne traccia a livello di codice se necessario, oppure eseguire alcune query globali e memorizzare nella cache i risultati.Dipenderà davvero dall'applicazione.

Direi che, a seconda di ciò che differenzia Persona e Persona speciale, probabilmente non vorrai il polimorfismo per questo compito.

Creerei una tabella Utente, una tabella Persona che ha un campo chiave esterna nullable per Utente (ovvero, la Persona può essere un Utente, ma non è necessario).
Quindi creerei una tabella SpecialPerson correlata alla tabella Person con eventuali campi aggiuntivi al suo interno.Se è presente un record in SpecialPerson per un dato Person.ID, si tratta di una persona speciale.

Nella nostra azienda ci occupiamo del polimorfismo combinando tutti i campi in un'unica tabella e il suo modello peggiore e senza integrità referenziale può essere applicato e molto difficile da comprendere.Consiglierei sicuramente questo approccio.

Vorrei utilizzare la tabella per sottoclasse ed evitare anche problemi di prestazioni, ma utilizzando ORM in cui possiamo evitare di unirci a tutte le tabelle delle sottoclassi creando query al volo in base al tipo.La strategia sopra menzionata funziona per il pull a livello di record singolo, ma per l'aggiornamento o la selezione collettiva non è possibile evitarlo.

sì, prenderei in considerazione anche un TypeID insieme a una tabella PersonType se è possibile che ci saranno più tipi.Tuttavia, se ce ne sono solo 3, non dovrebbe essere nec.

Questo è un post più vecchio ma ho pensato di intervenire dal punto di vista concettuale, procedurale e prestazionale.

La prima domanda che vorrei porre è la relazione tra persona, persona speciale e utente, e se è possibile per qualcuno essere Entrambi una persona speciale e un utente contemporaneamente.Oppure qualsiasi altra delle 4 combinazioni possibili (classe a + b, classe b + c, classe a + c o a + b + c).Se questa classe viene archiviata come valore in a type campo e quindi collasserebbe queste combinazioni, e tale collasso è inaccettabile, allora penserei che sarebbe necessaria una tabella secondaria che consenta una relazione uno-a-molti.Ho imparato che non lo giudichi finché non valuti l'utilizzo e il costo della perdita delle informazioni sulla combinazione.

L'altro fattore che mi fa propendere per un unico tavolo è la tua descrizione dello scenario. User è l'unica entità con un nome utente (ad esempio varchar(30)) e una password (ad esempio varchar(32)).Se la lunghezza possibile dei campi comuni è in media di 20 caratteri per 20 campi, l'aumento della dimensione della colonna è di 62 su 400, ovvero circa il 15% - 10 anni fa questo sarebbe stato più costoso di quanto non lo sia con i moderni sistemi RDBMS, specialmente con un tipo di campo come varchar (ad es.per MySQL) disponibile.

E, se la sicurezza ti preoccupa, potrebbe essere vantaggioso avere un tavolo uno a uno secondario chiamato credentials ( user_id, username, password).Questa tabella verrebbe richiamata in un JOIN contestualmente, ad esempio, al momento del login, ma strutturalmente separata da "chiunque" nella tabella principale.E, a LEFT JOIN è disponibile per le query che potrebbero voler prendere in considerazione gli "utenti registrati".

La mia considerazione principale da anni è ancora quella di considerare il significato dell'oggetto (e quindi la possibile evoluzione) al di fuori del DB e nel mondo reale.In questo caso, tutti i tipi di persone hanno cuori che battono (spero), e possono anche avere rapporti gerarchici tra loro;quindi, nel profondo della mia mente, anche se non adesso, potremmo aver bisogno di archiviare tali relazioni con un altro metodo.Questo non è esplicitamente correlato alla tua domanda qui, ma è un altro esempio dell'espressione della relazione di un oggetto.E ormai (7 anni dopo) dovresti avere comunque una buona idea di come ha funzionato la tua decisione :)

In passato l'ho fatto esattamente come suggerisci: avere una tabella Person per cose comuni, quindi SpecialPerson collegata per la classe derivata.Tuttavia, ci sto ripensando, poiché Linq2Sql vuole che un campo nella stessa tabella indichi la differenza.Tuttavia, non ho esaminato troppo il modello di entità: sono abbastanza sicuro che consenta l'altro metodo.

Personalmente, memorizzerei tutte queste diverse classi utente in un'unica tabella.Puoi quindi avere un campo che memorizza un valore "Tipo" oppure puoi implicare con quale tipo di persona hai a che fare in base ai campi compilati.Ad esempio, se UserID è NULL, questo record non è un utente.

Potresti collegarti ad altre tabelle utilizzando un tipo di join uno a uno o nessuno, ma in ogni query aggiungerai join aggiuntivi.

Il primo metodo è supportato anche da LINQ-to-SQL se decidi di seguire questa strada (lo chiamano "Table Per Hierarchy" o "TPH").

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top