Questa sera voglio parlarvi di un “dietro le quinte” di Entity Framework, ovvero una particolare situazione che mi è capitata durante le settimane scorse. Per la lettura di questo post assumo che sappiate:
- cosa sia un database
- cosa sia Entity Framework
- cosa sia un’applicazione Silverlight o giù di lì
- cosa siano i RIA Services
Dunque, cerco di arrivare al sodo in poche linee. Supponiamo di aver disegnato il seguente db:
Questa immagine arriva così com’è dal database diagram di SQL Server Management Studio. Forse è un database di cui avevo già parlato in passato in altri post. Abbiamo una tabella “Teams” contenente un po’ di squadre di calcio, ed abbiamo una tabella “Players” che mantiene l’elenco dei giocatori squadra per squadra. Il campo “IdTeam” della tabella “Players” serve proprio per mantenere questa relazione.
Ora, immaginiamo due scenari perseguibili:
- la tabella “Players” contiene solo giocatori che effettivamente appartengono ad una squadra
- la tabella “Players” contiene anche giocatori che NON appartengono ad alcuna squadra
Voi come realizzereste questa cosa? Il modo più semplice è quello di sfruttare in modo opportuno il campo “IdTeam” citato prima. Se “IdTeam” contiene NULL, quel record contiene un giocatore libero (non appartenente a nessuno), altrimenti contiene un ID che fa riferimento ad un Team esistente. Dal punto di vista del database, non ci sono particolari accrocchi da fare: è sufficiente impostare NULL / NOT NULL sul campo “IdTeam” ed il gioco è fatto (ed eventualmente rimuovere il valore predefinito dal campo stesso).
Come si comporta invece Entity Framework in questi due scenari?
Beh, devo dire che si comporta piuttosto bene. Ecco come appare il designer in entrambi i casi:
|
In questo primo caso, la relazione che lega le due entity è uno a molti.
Se nel designer selezioniamo IdTeam, vediamo che la proprietà Nullable è False. |
|
In questo secondo caso, la relazione è 0..1.
Se nel designer selezioniamo IdTeam, vediamo che la proprietà Nullable è (None). |
Una piccola precisazione. Ci sono almeno due piccole imperfezioni nella funzionalità di Update model from Database del designer di EF:
- non si accorge se cancellate un campo da una tabella (per adesso non ci riguarda)
- non si accorge se un certo campo diventa NULL / NOT NULL (questo invece ci riguarda eccome!)
Morale: se andate nel Management Studio e cambiate la proprietà Allow Nulls del campo “IdTeam” della tabella “Players”, e poi andate in VS2010 per refreshare il data model di EF, questi rimarrà invariato. Avete due possibilità: o cancellate la tabella e la riaggiungete (fattibile se le cose sono davvero semplici), oppure cambiate semplicemente la proprietà Nullable del campo (quest’ultima strada non l’ho provata).
Comunque sia, alla fine vi ritrovate il data model che rispecchia esattamente la struttura del vostro database.
E con le classi POCO, come siamo messi?
Da quando ho cominciato a lavorare con Silverlight 4 & EF4, ho subito utilizzato la possibilità di persistere oggetti generati tramite il template di classi POCO. Come si comporta questo template nei due casi esaminati sopra? Beh, anche qui la cosa è abbastanza intelligente!
Se il campo “IdTeam” non ammette NULL, allora è la proprietà è un semplice int.
Se il campo “IdTeam” ammette NULL, allora la proprietà IdTeam della nostra classe è una Nullable<int>, ed appare come segue:
public virtual Nullable<int> IdTeam
{
get { return _idTeam; }
set
{
try
{
_settingFK = true;
if (_idTeam != value)
{
if (Team != null && Team.Id != value)
{
Team = null;
}
_idTeam = value;
}
}
finally
{
_settingFK = false;
}
}
}
private Nullable<int> _idTeam;
Il codice qui sopra proviene dalla definizione della classe Player.
Caricare dati attraverso i RIA Services
Altro balzo in avanti. Supponiamo adesso di aver chiuso tutto il giro, e di aver implementato una piccola applicazione Silverlight che da qualche parte mostri una ListBox con l’elenco delle squadre: quando l’utente clicca su una squadra, viene eseguito un command che carica i giocatori di quella squadra, e li mostra in un’altra ListBox. Ora – non ci interessa sapere che il tutto è stato fatto con M-V-VM e tutto il resto.
Quello che conta è che da qualche parte il nostro codice sarà una cosa tipo questa:
void loadPlayersCommandExecute(object value)
{
if (value == null) return;
Team t = value as Team;
TeamsDomainContext dc = new TeamsDomainContext();
var query = dc.GetPlayersQuery().Where(pl => pl.IdTeam.Equals(t.Id));
dc.Load<Player>(query, callbackPlayers, null);
}
Questo è l’execute del command che carica i giocatori. Arriva in input un object, che è il SelectedItem della ListBox delle squadre.
Domanda per voi: se abbiamo impostato che “IdTeam” ammette NULL (che alla fin fine è lo scenario che ho applicato), questa riga di codice a runtime funziona? Per compilare compila, ma in esecuzione si schianta. E si schianta sull’ultima linea, al momento della chiamata a Load().
Perchè? Notare la clausola Where: la proprietà IdTeam di pl è Nullable<int>, mentre t.Id ritorna int.
In questo caso ho chiamato il metodo Equals su un reference type (Nullable<int>, o int? che dir si voglia), passando come parametro un value type (int). E tutto ciò non va affatto bene. L’exception contiene il seguente messaggio:
Expression of type ‘System.Int32’ cannot be used for parameter of type ‘System.Object’ of method ‘Boolean Equals(System.Object)’
Come risolvere la cosa. Basta sostituire la chiamata a Equals con il più banale ==. Ecco come diventa la query:
var query = dc.GetPlayersQuery().Where(pl => (pl.IdTeam == t.Id));
Ed il gioco è fatto!