NHibernate: le proprietà dell'elenco padre e le proprietà figlio correlate non sono sincronizzate
-
05-07-2019 - |
Domanda
Ho due oggetti correlati: ProgramSession e ProgramTask, con una relazione uno-a-molti. Una ProgramSession ha molti ProgramTasks. Quindi gli oggetti si presentano così:
public class ProgramSession
{
public virtual IList<ProgramTask> ProgramTasks
{
get { return _programTasks; }
set { _programTasks = value; }
}
}
public class ProgramTask
{
public virtual ProgramSession ProgramSession
{
get { return _programSession; }
set { _programSession = value; }
}
}
E i mapping ...
ProgramSession.hbm.xml
<bag name="ProgramTasks" lazy="false" cascade="all-delete-orphan" inverse="true" >
<key column="SessionUid"></key>
<one-to-many class="ProgramTask"></one-to-many>
</bag>
ProgramTask.hbm.xml
<many-to-one name="ProgramSession" column="SessionUid" class="ProgramSession" />
I problemi iniziano quando provo a cambiare la ProgramSession di un ProgramTask.
Se rimuovo ProgramTask dalla proprietà dell'elenco ProgramSession.ProgramTasks della vecchia sessione e quindi lo aggiungo alla stessa proprietà nella nuova sessione, NHibernate mi informa che verrà salvato un oggetto eliminato.
Se cambio semplicemente il valore dell'oggetto ProgramTask.ProgramSession, non ho problemi a salvare. Tuttavia, ottengo un comportamento strano se non si salva immediatamente perché le proprietà ProgramSession.ProgramTasks (su entrambe le sessioni) non vengono sincronizzate fino a quando non viene aggiornata la sessione NHibernate.
La modifica dell'oggetto ProgramTask.ProgramSession senza modificare direttamente anche gli elenchi, crea uno stato non valido. Prendi il seguente codice come esempio:
programTask.ProgramSession = newProgramSession;
Assert.That(newProgramSession.ProgramTasks.Contains(programTask)); // fails
Assert.That(!oldProgramSession.ProgramTasks.Contains(programTask)); // fails
Ciò è più problematico nel codice eseguito in seguito, presupponendo che le raccolte ProgramTasks siano sincronizzate con la proprietà ProgramSession. Ad esempio:
foreach(var programTask in programSession.ProgramTasks)
{
// whatever
}
Un trucco che ho usato per aggirare questo è stato quello di interrogare l'elenco. Non posso usarlo ovunque, ed è chiaramente una cattiva soluzione, ma sottolinea il problema:
var tasksActuallyInSession =
programSession.ProgramTasks
.Where(task => task.ProgramSession == programSession)
.ToList();
Esiste un modo per gestire questo tipo di situazione? Una buona pratica? Sto facendo qualcosa di sbagliato? La mappatura è errata? C'è qualche bandiera NHibernate super segreta che devo impostare?
Soluzione
Non sono sicuro di aver capito tutto quello che stai facendo qui. Alcuni pensieri:
Se decidi di spostare ProgramTasks
, diventano indipendenti e non dovrebbero essere mappati usando cascade = " all-delete-orphan "
. Se lo fai, NH rimuove il ProgramTask
dal database quando lo rimuovi da un ProgramSession
.
Mappalo usando cascade = " none "
e controlla tu stesso il ciclo di vita dell'oggetto. (ciò significa: memorizzalo prima che venga archiviato un ProgramSession
. Eliminalo quando non viene più utilizzato.)
Non sono sicuro che anche questo sia un problema, ma nota che se hai un riferimento inverso, il tuo codice è responsabile di rendere coerenti i riferimenti. Naturalmente, i riferimenti vengono ripuliti dopo essere stati archiviati nel database e caricati in una sessione vuota, questo perché nel database è presente una sola chiave esterna. Ma non è così che dovrebbe essere fatto. NH non è responsabile della gestione dei riferimenti. (La sua unica responsabilità è di persistere in ciò che stai facendo in memoria.) Quindi devi renderlo coerente in memoria e implementare la tua logica aziendale come se non ci fosse NHibernate dietro.