Relazione uno-a-molti (incasinatissima)

rated by 0 users
This post has 13 Replies | 3 Followers

Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907
petrux Posted: 07-22-2010 22.04

Ciao a tutti,

 

ho una situazione del genere:

 

class Item { ... }

class Foo {

    ICollection<Item> ItemSet { ... }

}

class Bar {

    ICollection<Item> ItemSet { ... }

}

 

dunque, c'è una relazione uno-a-molti nel senso che un Foo/Bar ha diversi Item, tuttavia nella tabella che mappa Item non dovrebbero esserci foreign keys a Foo/Bar. Inoltre un Item può essere associato ad un solo Foo o un solo Bar. Come posso mappare una situazione del genere usando Nhibernate? Avete qualche link? In particolare, come posso implementare il vincolo di unicità "incrociata"? O devo necessariamente gestirlo a livello di business? 

 

Grazie in anticipo,

Giulio

Top 10 Partecipanti
Maschio
Post 243
Punteggio 3.383

La prima idea che mi viene in mente.

Fai ereditare Foo e Bar da una classe base, diciamo Base, li ci metti la ItemSet.

Item ha una proprietà di tipo Base, che punta o a un Foo o a un Bar.

La collection ItemSet di Foo e Bar è mappata con Inverse = true, ovvero non viene usata per la relazione, mentre la proprietà Base di Item è quella che tiene le relazioni. Nel Setter della proprietà base di Item, fai il controllo sul grafo per tenere tutto corretto, ovvero se l'item è collegato ad un Base, tu prima di collegarlo al nuovo Base, togli se stesso dalla colection ItemSet del base correlato, cambi la proprietà e reinserisci nella collezione (si trovano esempi di codice in giro). Naturalmente da fuori non deve essere possibile aggiungeer o togliere elementi direttamente dalla ItemSet, ma devi magari avere in Base un metodo tipo "AddItem" che internamente fa una cosa del tipo

item.Base = this

scatenando tutta la logica di update detta prima.

Spero di essere stato più o meno chiaro :S

alk.

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907

Ciao Gian Maria,

Gian Maria Ricci:

La prima idea che mi viene in mente.

Fai ereditare Foo e Bar da una classe base, diciamo Base, li ci metti la ItemSet.

Item ha una proprietà di tipo Base, che punta o a un Foo o a un Bar.

La collection ItemSet di Foo e Bar è mappata con Inverse = true, ovvero non viene usata per la relazione, mentre la proprietà Base di Item è quella che tiene le relazioni. 

 

Uhm... parti dal presupposto che sono nella situazione in cui gli Id di Foo e Bar sono di due tipi diversi. Come faccio a mapparli? Comunque, a riguardo, apro subito un altro thred, perché qui, altrimenti andremmo OT. 

 

Gian Maria Ricci:

Nel Setter della proprietà base di Item, fai il controllo sul grafo per tenere tutto corretto, ovvero se l'item è collegato ad un Base, tu prima di collegarlo al nuovo Base, togli se stesso dalla colection ItemSet del base correlato, cambi la proprietà e reinserisci nella collezione (si trovano esempi di codice in giro). Naturalmente da fuori non deve essere possibile aggiungeer o togliere elementi direttamente dalla ItemSet, ma devi magari avere in Base un metodo tipo "AddItem" che internamente fa una cosa del tipo

item.Base = this

scatenando tutta la logica di update detta prima.

Spero di essere stato più o meno chiaro :S

 

Si, sei stato chiarissimo.

Questo però significa che: 

- i vincoli di unicità (e integrità referenziale) li devo gestire a livello di business: il db non garantisce

- bisogna necessariamente usare una superclasse. Confesso che l'idea del mapping polimorfico non mi è ancora chiarissima. In particolare la situazione che sto fronteggiando è *incasinatissima* (due classi Foo e Bar che vengono da due fonti differenti, hanno id di tipo diverso)

Suggerimenti?

 

Grazie e buona giornata,

petrux

Top 10 Partecipanti
Maschio
Post 243
Punteggio 3.383

Avere due entità con id differenti è un po un casino :(, se arrivano da due fonti differenti e non hai proprio modo di cambiarlo, puoi sempre provare il mapping <any> anche se penso che con id differenti non sia nemmeno possibile.

L'unicità è implicita, nel senso che un Item se ha una sola foreign key non potrà mai e poi mai essere legata a due differenti oggetti, però se tali oggetti hanno id differenti, non puoi mettere la foreign key, per cui probabilmente devi passare per una tabella intermedia... ma bisognerebbe a questo punto ragionare su un caso pratico.

Per il resto io nel database l'integrità delle foreign key la lascio sempre comunque attiva :).

alk.

 

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907

Cia{o Alk,

 

Gian Maria Ricci:

Avere due entità con id differenti è un po un casino :(, se arrivano da due fonti differenti e non hai proprio modo di cambiarlo, puoi sempre provare il mapping <any> anche se penso che con id differenti non sia nemmeno possibile.

 

Guarda, ho fatto una prova per togliermi uno sfizio.

interface IItemContent {...}

class Foo : IItemContent { ... }

class Bar : IItemContent { ... }

class Item 

{

    IItemContent Content { ... }

}

 

ovviamente la property 'Content' di Item è mappata con un any.

Ora, prova a fare con NHibernate una query del tipo:

 

ItemContent c = ...;

session.Query<Item>().Where(i => i.Content == c);

 

e goditi l'eccezione.

Ma questo, oltre a farmi perdere un sacco di tempo, mi fa rilettere: possibile che in tutto l'universo degli sviluppatori .NET *mai nessuno* si sia trovato in una situazione simile?

 

Gian Maria Ricci:

L'unicità è implicita, nel senso che un Item se ha una sola foreign key non potrà mai e poi mai essere legata a due differenti oggetti, però se tali oggetti hanno id differenti, non puoi mettere la foreign key, per cui probabilmente devi passare per una tabella intermedia... ma bisognerebbe a questo punto ragionare su un caso pratico.

 

Caso pratico? Eccolo qua. Sistema di gestione inviti: ho un oggetto Invitation che dice se un omino parteciperà o no all'invito che gli è stato rivolto. Gli omini sono entità che implementano l'interfaccia IOmino (nome, cognome, blablabla). Alcuni sono presi da una vista su un db legacy con un ID a Stringa, gli altri su un'altra tabella (e non una vista) con un Id intero.

 

interface IOmino { ... }

class OminoSuVista { ... } //Id strings

class Omino { ... } //Id a Int32

class Invitation {

    bool WillAttend { ... }

    IOmino Omino { ... }

}

 

Sono arrivato a *giustificare* un design che a me pare pessimo, ossìa quello di avere una una relazione molti-a-molti "fasulla" tra Omino e Invitation, e una simile tra OminoSuVista e Invitation. Questo perché, de facto, è impossibile modellare a livello relazionale una problematica *assolutamente banale* e nel caso mio caso anche ricorrente. Oppure, e questo è assai probabile, semplicemente non lo so fare io, e non ho trovato fonti in giro.

 

Gian Maria Ricci:

Per il resto io nel database l'integrità delle foreign key la lascio sempre comunque attiva :).

 

Non ho capito...

 

Ciao e grazie,

Giulio

Top 10 Partecipanti
Maschio
Post 243
Punteggio 3.383

Ciao, visto che sono in questo periodo un po incasinato, se hai fatto la prova puoi mandarmi il codice? cosi provo a vedere direttamente sul caso pratico cosa viene fuori.

Il problema è che avere entità con id di tipo differente e volerli legare ad una altra entità non è proprio un problema banale. Gli ORM supportano abbastanza bene i Db Legacy, ma sono fatti in generale per mappare un dominio fatto prima del db, per cui nessun designer utilizzerebbe id di tipo differente.

La situazione è differente quando invece il db è dato, e tu devi mapparci sopra della roba ;(.

petrux:

Gian Maria Ricci:

Per il resto io nel database l'integrità delle foreign key la lascio sempre comunque attiva :).

Non ho capito...

Nel db io le Foreignk key le lascio, se tra cliente ed ordine ho una relazione e la tabella ordine chiaramente ha un campo che è tipo  customerId, la foreignk key nel db la faccio, in modo che se dall'ORM si cerca di fare casino il db non accetti il dato. Nel tuo caso però non riesci nemmeno a metterla la foreign key perchè non riesci a capire di che tipo sia, dato che deve linkare o a un id intero oppure ad una stringa.

Se mi mandi l'esempietto cosi non perdo tempo a rifare tutto io ci do uno sguardo comunque :) prometto :)

 

alk.

 

 

  • | Punteggio Post: 20
Top 25 Partecipanti
Maschio
Post 30
Punteggio 678

Ciao Giulio,

petrux:

Caso pratico? Eccolo qua. Sistema di gestione inviti: ho un oggetto Invitation che dice se un omino parteciperà o no all'invito che gli è stato rivolto. Gli omini sono entità che implementano l'interfaccia IOmino (nome, cognome, blablabla). Alcuni sono presi da una vista su un db legacy con un ID a Stringa, gli altri su un'altra tabella (e non una vista) con un Id intero.

 

interface IOmino { ... }

class OminoSuVista { ... } //Id strings

class Omino { ... } //Id a Int32

class Invitation {

    bool WillAttend { ... }

    IOmino Omino { ... }

}

Provo a sparare, magari dico cavolate: e se provassi a mappare la relazione su due campi separati, ed ad "astrarla" a livello di dominio? Mi spiego meglio...
Sul db, crei la tua tabella Invitation con le colonne

  • Id (int, o quel che è)
  • WillAttend (bit)
  • IdOminoStandard (int, nullable, foreign key verso la tabella Omini)
  • IdOminoLegacy (string, nullable, foreign key verso la vista OminiLegacy) 

L'oggetto di dominio Invitation, a sua volta, avrà 2 proprietà (credo si possano fare anche Protected, ma non ne sono sicuro), OminoStandard e OminoLegacy, mappate nel modo consueto, ed una proprietà pubblica Omino soltanto in Get che restituisce uno dei due omini...

class Invitation {

    bool WillAttend { ... }

    IOmino Omino {  get { return OminoStandard != null ? OminoStandard : OminoLegacy; } }

    IOmino OminoStandard { ... } 
    IOmino OminoLegacy { ... }  

in questo modo l'integrità delle foreign key resta garantita anche a livello di DB: devi giusto fare un po' più di attenzione in fase di creazione dell'Invitation per evitare di creare Invitation non associate ad alcun Omino (per stare tranquilli basta inserire una regola di validazione sul DAL che validi l'invitation prima che questa venga salvata sul repository ).

inoltre, i client potranno utilizzare la proprietà Omino per accedere in modo del tutto trasparente all'omino associato all'invitation, sia esso del primo o del secondo tipo...

Può andare?

Ciao,
neronotte 

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 243
Punteggio 3.383

neronotte:
L'oggetto di dominio Invitation, a sua volta, avrà 2 proprietà (credo si possano fare anche Protected, ma non ne sono sicuro), OminoStandard e OminoLegacy, mappate nel modo consueto, ed una proprietà pubblica Omino soltanto in Get che restituisce uno dei due omini...

Si può fare, nhibernate mappa campi privati e protected :) e concordo con te che questa è la soluzione sicuramente migliore. Quando ci sono problemi di questo tipo, la cosa migliore che puoi fare è quella di risolvere il problema a livello di domain model. Per cui anche io sono pienamente daccordo con neronotte, il domain model serve appunto per nascondere le complessità negli oggetti.

Alk.

 

  • | Punteggio Post: 5
Top 10 Partecipanti
Maschio
Post 243
Punteggio 3.383

petrux:

Guarda, ho fatto una prova per togliermi uno sfizio.

interface IItemContent {...}

class Foo : IItemContent { ... }

class Bar : IItemContent { ... }

class Item 

{

    IItemContent Content { ... }

}

Ho fatto velocemente anche io una prova e come sospettavo il mapping Any non funziona perchè chiaramente lui vuole il tipo dell'id memorizzato e quindi o usi l'uno o usi l'altro.

Se non puoi proprio cambiare il database, perchè è legacy purtroppo si può fare poco. Ma d'altra parte pensa anche al di fuori di un ORM. Se tu hai due tabelle, una con chiave primaria intera e l'altra con chiave primaria stringa chiamate FooTable e BarTable e vuoi legare una ItemTable ad una delle due, che soluzione faresti a livello relazionale?

Alk.

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907

Ciao Alk,

Gian Maria Ricci:

Ciao, visto che sono in questo periodo un po incasinato, se hai fatto la prova puoi mandarmi il codice? cosi provo a vedere direttamente sul caso pratico cosa viene fuori.

 

Ho fatto tutte le prove del caso. A occhio ti direi che, con i miei strumenti, non ne esco vivo. :-)

L'unica cosa che mi rimane da fare è cercare di usare un "adapter" (tipo quelli che si usano per gli enum) per l'Id. Ma devo ancora studiare bene (considerate che ho approcciato NHibernate da 4 settimane appena e solo nei ritagli di tempo). 

 

Gian Maria Ricci:

Il problema è che avere entità con id di tipo differente e volerli legare ad una altra entità non è proprio un problema banale. Gli ORM supportano abbastanza bene i Db Legacy, ma sono fatti in generale per mappare un dominio fatto prima del db, per cui nessun designer utilizzerebbe id di tipo differente.

 

Lo so. Ma la vita ti mette di fronte a queste situazioni... :-)

 

Ciao e grazie,

Giulio

Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907

Ciao Alk,

 

Gian Maria Ricci:

Se non puoi proprio cambiare il database, perchè è legacy purtroppo si può fare poco. Ma d'altra parte pensa anche al di fuori di un ORM. Se tu hai due tabelle, una con chiave primaria intera e l'altra con chiave primaria stringa chiamate FooTable e BarTable e vuoi legare una ItemTable ad una delle due, che soluzione faresti a livello relazionale?

 

Due relazioni molti-a-molti con vincoli "unique" sulle singole colonne.

Ma poi a me faceva paicere avere a livello di object model la proprietà 'Content', che mappasse a un qualsiasi IItemContent.

Con una strategia table-per-concrete class si risolve alla grande. Odevo approfondire un po' la cosa... <g>

 

Ciao,

Giulio

Top 10 Partecipanti
Maschio
Post 243
Punteggio 3.383

petrux:
Due relazioni molti-a-molti con vincoli "unique" sulle singole colonne.

per questo ti suggerisco la soluzione che ha dato neronotte, fai due proprietà nell'oggetto una a foo ed una a bar, le fai protected cosi da fuori non si vedono. Poi fai la proprietà ItemContent che internamente usa le due proprietà sottostanti.

petrux:
Con una strategia table-per-concrete class si risolve alla grande. Odevo approfondire un po' la cosa

non perderci tempo, con id di tipo differente le gerarchie non riesci a gestirle ;), secondo me spendi del tempo inutilmente.

Potresti provare usando un generatore di id custom ma ti vai a complicare la vita per me e non so nemmeno se ne riesci ad uscire vivo.

alk.

  • | Punteggio Post: 5
Top 10 Partecipanti
Maschio
Post 243
Punteggio 3.383

Ho fatto al volo al volo un esempio veloce, che dovrebbe funzionare (è veramente fatto velocemente :) ) che ti fa vedere una possibile soluzione al problema.

http://www.nablasoft.com/Temp/NhGuisaSample1.7z

Alk.

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907

Ciao Alk,

 

Gian Maria Ricci:

Ho fatto al volo al volo un esempio veloce, che dovrebbe funzionare (è veramente fatto velocemente :) ) che ti fa vedere una possibile soluzione al problema.

 

Grazie.

Appena scaricato.

Ti farò sapere ASAP.

 

Ciao,

Giulio

  • | Punteggio Post: 5
Pagina 1 di 1 (14 elementi) | RSS
Powered by Community Server (Commercial Edition), by Telligent Systems