Utilizzare le stored procedure con Entity Framework

Stefano Mostarda

di Stefano Mostarda, in LINQ, 10 febbraio 2009

3 pagine in totale: <<Indietro 1 [2] 3 Avanti >>

Il primo passo consiste nel cliccare con il tasto destro sul designer e selezionare la voce "Update Model from Database". Nella form che si apre basta espandere il nodo "Stored Procedures" e selezionare le stored procedure che si vogliono utilizzare. A questo punto le stored procedure sono importatate nello schema fisico dell 'EDM. il secondo passo è importarle nello schema logico ed eventualmente mapparle con una classe. Fortunatamente il designer ci viene in aiuto in questa semplice operazione. Nella finestra "Model Browser" basta cliccare con il tasto destro sulla stored procedure e selezionare la voce "Create Function Import". A questo punto nel popup che si apre basta selezionare la classe associata alla stored procedure ed il tutto è pronto.

importare e mappare una funzione

Il designer genera già il codice per invocare la stored procedure all'interno della classe che fa da contesto quindi il suo utilizzo è veramente banale. In questo caso, la stored procedure GetOrders restituisce tutti gli ordini.

using (SPEFEntities ctx = new SPEFEntities()){ 
  var data = ctx.GetOrders(); 
  ... 
}

Ma cosa fare quando la stored procedure torna dei nomi di colonna diversi rispetto alle proprietà della classe? Questo scenario si verifica quando una volta importato il modello dal database si modificano i nomi delle proprietà per renderle più comprensibili. In tal caso, se la stored procedure è utilizzata solo dalla nostra applicazione, si può modificare la select cambiando i nomi delle colonne. Nel caso la stored procedure sia già utilizzata da altre applicazioni questa soluzione è impraticabile.

Un'alternativa consiste nel creare una nuova stored procedure che contenga i nomi delle colonne corretti, ma questo complica la manutenibilità del database.

Un'altra possibilità consiste nel creare una classe che abbia le proprietà che corrispondono ai nomi delle colonne del risultato ottenuto dalla stored procedure. A questo punto si utilizza questa classe per riversare da codice i dati nella classe Order. Questo è un hack poco consigliabile perché comunque risolve il problema del mapping ma introduce altre complessità.

Un'ultima alternativa consiste nel creare una funzione direttamente nell'EDM. Questa caratteristica è l'equivalente del creare una nuova stored procedure con la differenza che invece che dichiararne una nuova sul database, la definiamo all'interno della nostra applicazione. Nel caso il DBA voglia a tutti i costi mantenere sotto controllo l'SQL eseguito, questa soluzione non è applicabile poiché il codice della funzione deve essere incluso nello storage schema dell'EDM e quindi non gestibile da lui.

Leggere i dati in presenza di ereditarietà

A seconda dei casi, l'ereditarietà ha un costo quando si parla di stored procedure. Partiamo dal caso più semplice ovvero mappare la stored procedure che restituisce tutte le carte di credito di un utente. In questo caso, il processo è identico a quello visto in precedenza. Anche se la classe CreditCard, su cui la stored procedure mappa, fa parte di una gerarchia di ereditarietà, Entity Framework non ne è minimamente interessato in quanto istanzia direttamente la classe ignorando le altre.

Ben più complessa è la situazione quando si vogliono ritrovare diversi tipi di oggetto e si decide di mappare la stored sulla classe base. Nel nostro caso si supponga di voler ritrovare tutti i metodi di pagamento censiti per un utente. Visto che i metodi di pagamento sono rappresentati dalle classi CreditCard e BankTransfer, la stored procedure deve mappare sul tipo base Payment.

Sfortunatamente, questa caratteristica non è presente nel designer di Visual Studio. Quando si mappa una stored procedure verso una classe, solo quella classe viene presa in considerazione e l'ereditarietà non entra in gioco. Per poter mappare il risultato della stored procedure verso l'intera gerarchia bisogna intervenire a mano nella sezione di mapping. Il tutto funziona in base ad una colonna che funge da discriminator. In base al valore della colonna, si decide quale classe istanziare.

&lt;FunctionImportMapping FunctionImportName="GetPaymentsByUser"  
  FunctionName="SPEFModel.Store.GetPaymentsByUser"&gt; 
  &lt;ResultMapping&gt; 
    &lt;EntityTypeMapping TypeName="SPEFModel.CreditCard"&gt; 
      &lt;Condition ColumnName="PaymentType" Value="1"/&gt; 
    &lt;/EntityTypeMapping&gt; 
    &lt;EntityTypeMapping TypeName="SPEFModel.BankTransfer"&gt; 
      &lt;Condition ColumnName="PaymentType" Value="2"/&gt; 
    &lt;/EntityTypeMapping&gt; 
  &lt;/ResultMapping&gt; 
&lt;/FunctionImportMapping&gt;

In questo modo stabiliamo che se la colonna PaymentType del resultset ottenuto ha valore 1, allora si istanzia un oggetto di tipo CreditCard. Se invece la colonna PaymentType ha valore 2 allora si istanzia un oggetto di tipo BankTransfer. La classe base Payment non è nemmeno presa in considerazione.

Tuttavia c'è un cavillo da tenere a mente. Se la classe base, Payment in questo caso, è astratta, la chiamata alla stored procedure genera un'eccezione. Questo è un bug dovuto al modo in cui funziona il processo di trasformazione del resultset in oggetti. L'unica possibilità per far funzionare il tutto è rendere la classe base concreta. Questo comporta alcune modifiche nel mapping tra le classi che fanno parte della gerarchia e la tabella che contiene i dati oltre alla necessità di assicurarsi che nel codice la classe base non venga istanziata.

Una gerarchia mappata su una tabella con la strategia Table-Per-Hierarchy si presta ad essere facilmente mappata anche nei confronti di una stored procedure. Infatti, la presenza di una colonna che fa da discriminatore è una caratteristica di questa strategia di mapping. Le cose cambiano quando si ha a che fare con una strategia che prevede il Table-Per-Type. In questo caso, ad ogni classe corrisponde una tabella. Questo significa che per capire se un record nella tabella va mappato su una classe piuttosto che su un'altra, non bisogna ricorrere ad un discriminatore ma bastano le join tra tabelle. Se si vuole ritrovare un'entità che sia di un solo tipo (Car o Mortorbike) le cose funzionano esattamente come per qualunque altro tipo in quanto l'ereditarietà non entra in gioco. Nel caso in cui invece si vogliano trovare tutti i veicoli, la situazione cambia. Quando si utilizzano stored procedure, poiché il risultato finale è sempre e comunque un resultset, Entity Framework non sa nulla delle join fatte all'interno di questa e quindi si deve comunque ricorrere ad un discriminatore per identificare quale riga corrisponde a quale tipo.

Poiché non abbiamo a disposizione un discriminatore nelle tabelle, ce lo dobbiamo inventare verificando i campi nulli.

select v.VehicleId, Name, AirConditioning, Stereo, Bag,  
  case when ISNULL(bag, '') = '' then 'C' else 'M' end as type 
from vehicle v 
left join car c on (v.vehicleid = c.vehicleid) 
left join motorcycle m on (v.vehicleid = m.vehicleid)

In questa select si ritrovano i dati di tutte le vetture e di tutte le moto. Poiché a causa dell'outer join nella riga dell'auto i campi relativi alla moto sono nulli (e viceversa), lavorando sui valori nulli si può ricavare il tipo di una riga. Ovviamente, se ci sono più tabelle e quindi più tipi interessati, la CASE per recuperare il tipo si fa più complessa perché i campi nulli da testare aumentano.

A questo punto abbiamo estratto tutti i campi necessari per mappare le entità. Nuovamente, dobbiamo importare la stored procedure dal database e poi mapparla assegnando il risultato al tipo base Vehicle. Infine dobbiamo andare a mano nel file di mapping ed inserire le informazioni sull'ereditarietà esattamente come si è fatto nell'esempio precedente.

&lt;FunctionImportMapping FunctionImportName="GetVehicles"  
  FunctionName="SPEFModel.Store.GetVehicles"&gt; 
  &lt;ResultMapping&gt; 
    &lt;EntityTypeMapping TypeName="SPEFModel.Car"&gt; 
      &lt;Condition ColumnName="Type" Value="C"/&gt; 
    &lt;/EntityTypeMapping&gt; 
    &lt;EntityTypeMapping TypeName="SPEFModel.Motorcycle"&gt; 
      &lt;Condition ColumnName="Type" Value="M"/&gt; 
    &lt;/EntityTypeMapping&gt; 
  &lt;/ResultMapping&gt; 
&lt;/FunctionImportMapping&gt;

Recuperare dati custom

Molto spesso le stored procedure effettuano delle elaborazioni e ritornano resultset complessi che non rappresentano nessuna entità oppure sono frutto di join tra diverse tabelle e quindi ancora una volta non hanno una correlazione con una singola entità. Questa tipologia di dato è per sua natura in sola lettura. Molto spesso si utilizzano addirittura delle viste preconfigurate per accedere a questa tipo di dati. Il modo migliore per trattare questi dati è creare una classe apposita senza ricorrere alla modifica di quelle correnti. Una volta creata la classe nel designer, però, bisogna per forza mapparla con una tabella o una vista sul database. Il problema è che non avendo sul database un oggetto con questa struttura, non possiamo eseguire il mapping. La cosa più semplice da fare in questi casi, è creare nel database una vista fittizia che abbia la stessa struttura dei dati restituiti dalla stored procedure e poi mappare la classe verso questa vista. Questo mapping è inutile funzionalmente, ma obbligatorio per Entity Framework che ci obbliga a mappare ogni classe.

3 pagine in totale: <<Indietro 1 [2] 3 Avanti >>

Contenuti dell'articolo

Commenti

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.



Segnala su: Facebook MSDN Social Twitter Segnalo Wikio Diggita Technorati Stumbleupon Google Yahoo FriendFeed Delicious Furl

TUTORIALS
TOP TEN ARTICOLI
ARTICOLI VIA E-EMAIL

Iscriviti alla nostra newsletter nuoviarticoli per ricevere via e-mail le notifiche!

MEDIA
IN EVIDENZA
MISC