Chi arriva a Entity Framework partendo da Linq To SQL probabilmente dopo aver apprezzato molte migliorie come ad esempio il modello più vicino ad un ObjectModel quale ci si aspetta, probabilmente cadrà in un fase “depressiva” perchè si rende conto che oltre ad aver guadagnato qualcosa, nel contempo ha anche perso molto.
Non sto ad elencarvi questi casi, ma piuttosto mi vorrei concentrare su quello che mi è capitato oggi e che mi ha fatto perdere un bel po’ di tempo. In buona sostanza per Entity Framework esistono delle stored procedure “regolari” e altre per così dire “fuori ordinanza”. Se voi avete una stored procedure che vi restituisce un resultset sovrapponibile con una delle entity del vostro dominio allora non c’è problema, siamo nel campo delle procedure ammesse. Ma se per caso come oggi mi è capitato dovete fare una procedura che vi restituisce uno scalare o anche nulla, allora non c’è niente da fare... il designer di Entity Framework si rifiuta di prenderla in considerazione. O meglio: fa finta di fare il suo dovere ma poi vi molla sul più bello e cioè quando c'è da generare il codice che mappa la procedura nell'Object Context.
Per carità, se mettiamo sul piatto della bilancia i vantaggi rispetto agli svantaggi ce ne a sufficienza per prendere in considerazione EF, ma di fronte a queste cose verrebbe voglia di...
Comunque ecco come fare per gestire questo tipo di evenienze. Innanzitutto dovete creare la partial class che si sovrappone al vostro EDMX.
1: public partial class MyEntities
2: {
3: }
Così facendo possiamo ora aggiungere la nostra procedura mappando i parametri su una stored procedure. Poniamo ad esempio di dover chiamare una sp denominata sp_Message_Post. Passiamo al designer e come di consueto mappiamo la procedura ricavandola dal Database con "Update model from database". Una volta che esce la lista delle procedure nel tab "Add" mettiamo la spunta sulla procedura e confermiamo l'aggiornamento.
A questo punto dobbiamo creare il "function import". Si tratta in buona sostanza dell'operazione con cui di informa il designer che una certa procedura va mappata su un metodo dell'ObjectContext e soprattuto che essa avrà un certo tipo di valore di ritorno. Nel Model browser si seleziona la procedure e con il tasto destro si sceglie "Create function Import". Diamo un nome a questa funzione, ad esempio "PostMessage" e un tipo di valore di ritorno, nel nostro caso nessuno.
Ed è qui che accade la cosa sconcertante. Se specificate che la vostra procedura ha un valore di ritorno scalare o non ce l'ha proprio il designer si considera sollevato dell'onere di creare il metodo nell'ObjectContext mentre solo nel caso in cui il valore sia una Entity allora il designer di degna di fare il proprio lavoro come si deve.
Allora ecco il codice da scrivere per immettere un metodo che wrappa il function import appena creato:
1: public void PostMessage(
2: int companyId,
3: byte messageType,
4: byte priority,
5: byte deviceType,
6: string recipient,
7: string sender,
8: DateTime dateCreated,
9: byte[] messageBinary)
10: {
11: EntityParameter companyIdParameter =
12: new EntityParameter("companyId", DbType.Int32);
13: companyIdParameter.Value = companyId;
14:
15: // e così via per tutti i parametri...
16:
17: this.ExecuteFunction(
18: "PostMessage",
19: companyIdParameter,
20: messageTypeParameter,
21: priorityParameter,
22: deviceTypeParameter,
23: recipientParameter,
24: senderParameter,
25: dateCreatedParameter,
26: messageBinaryParameter);
27: }
Potrebbe sembrare tutto, ma manca ancora un pezzetto. Bisogna infatti create una versione nuova del metodo ExecuteFunction che crei il command e faccia la chiamata, non alla procedura in questione ma al function import. Ecco il codice:
1: protected void ExecuteFunction(string functionName, params EntityParameter [] parameters)
2: {
3: DbCommand command = this.Connection.CreateCommand();
4: command.CommandType = CommandType.StoredProcedure;
5: command.CommandText = this.DefaultContainerName + '.' + functionName;
6:
7: foreach (EntityParameter p in parameters)
8: command.Parameters.Add(p);
9:
10: if (command.Connection.State == ConnectionState.Closed)
11: command.Connection.Open();
12:
13: try
14: {
15: command.ExecuteNonQuery();
16: }
17: finally
18: {
19: command.Connection.Close();
20: }
21: }
In buona sostanza dobbiamo tornare ad "abbassarci" a gestire in proprio la connessione. Creiamo il command, assegnamo il nome del function import prependendo il "DefaultContainerName" cioè il namespace in cui vengono generati gli oggetti. In seguito aggiungiamo i parametri al comando e poi eseguiamo il tutto prendendoci l'onere di aprire la connessione se non lo è già e di chiuderla non appena terminato.
Tutto sommato è un po' di codice cui sicuramente non siamo nuovi. Ma la domanda è: perchè il designer non si assume questo onere per noi?
Technorati Tag:
Entity Framework