- LINQ - Prof. Mauro Giacomini Anno Accademico: 2008-2009 Cos’è Cos è LINQ? LINQ (Language-Integrated Query) è un insieme di funzionalità di Visual Studio 2008 (ed è quindi un’innovazione del framework 3.5) che estende le potenzialità di esecuzione di query alla sintassi dei linguaggi C# e Visual Basic. LINQ introduce modelli standard di facile apprendimento per ll'esecuzione esecuzione di query e ll'aggiornamento aggiornamento dei dati, dati consentendo l'estensione della tecnologia per supportare praticamente qualsiasi tipo di archivio dati. Introduzione a LINQ in Visual Basic U a que Una query yèu un'espressione esp ess o e cche e recupera ecupe a da dati da u un'origine o g e da dati. Le query sono espresse in un linguaggio di query dedicato. Nel tempo sono stati sviluppati diversi linguaggi per i vari tipi di origini dati, ad esempio SQL per database relazionali e XQueryy p per XML. Lo sviluppatore pp di applicazioni pp deve pertanto imparare un nuovo linguaggio di query per ogni tipo di origine dati o formato dati supportato. LINQ (Language-Integrated Query) semplifica la situazione offrendo un modello coerente per l'utilizzo dei dati con tutti i diversi tipi di origini e formati dati. Le operazioni di query LINQ sono costituite da tre azioni: • Ottenere l'origine o le origini dati. • Creare la query. • Eseguire la query. In LINQ l'esecuzione di una query è distinta dalla creazione della query. I dati non vengono recuperati solo creando una query. Query • Origine dei dati LINQ consente di eseguire query su dati in un database SQL Server, Server XML, XML matrici e insiemi in memoria, dataset ADO.NET o qualsiasi altra origine dati remota o locale che supporta LINQ. È possibile fare tutto per questo con i comuni elementi del linguaggio Visual Basic. Poiché le query sono scritte in Visual Basic, i risultati vengono restituiti come oggetti f fortemente tipizzati. Questi Q oggetti supportano IntelliSense, S il che consente di scrivere il codice più velocemente e di individuare errori nelle query in fase di compilazione anziché in fase di esecuzione. • Creazione della query Nella query vengono specificate le informazioni da recuperare dall'origine o dalle origini dati. È inoltre possibile specificare il modo in cui ordinare, raggruppare o strutturare le informazioni prima che vengano restituite. L'espressione di query contiene tre clausole: From, Where e Select. • Esecuzione della query q y Una query può essere eseguita appena definita (esecuzione immediata) oppure è possibile archiviare la definizione ed eseguire la query in un secondo momento ((esecuzione p posticipata). p ) Fasi di un un’operazione operazione di query - esempio ‘C Creazione i d della ll sorgente t d deii d datiti Dim db = New Northwind("c:\northwind\northwnd.mdf") ‘Creazione Creazione della query Dim londonCustomers = From cust In db.Customers _ Where cust.City = "London" _ Select cust ‘Esecuzione (posticipata) della query For Each cust in londonCustomers Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City) Next Provider LINQ • Un provider LINQ Visual Basic esegue il mapping di query LINQ sull'origine dati su cui eseguire una query. query • Quando si scrive una query LINQ, il provider prende tale query e la traduce in comandi che l'origine dati sarà in grado di eseguire. • ll provider converte anche i dati dall'origine dall origine negli oggetti che costituiscono il risultato della query. • Infine, converte gli oggetti in dati quando si inviano aggiornamenti all'origine dati. Visual Basic include i seguenti provider LINQ LINQ to Objects LINQ to SQL LINQ to DataSet LINQ to XML Operatori di query LINQ in Visual Basic • Clausola FROM Per iniziare una query è obbligatoria una clausola From o una clausola Aggregate. La clausola From specifica un insieme di origine e una variabile di iterazione per una query. Dim names = From cust In customers Where cust.State = “WA” Select cust.Companyname ‘Rit ‘Ritorna il nome dell’azienda d ll’ i d per tutti i clienti il cui stato è uguale a ‘WA’ • Clausola SELECT Facoltativo Dichiara un insieme di variabili di iterazione per una query. Facoltativo. query ‘Ritorna il nome dell’azienda ed il valore dell’ID per ogni cliente come una collection di un nuovo tipo Dim customerlist = From cust In customers Select cust.Companyname, cust.ID Se non è specificata alcuna clausola Select, le variabili di iterazione per la query consistono nelle ll variabili i bili di ititerazione i specificate ifi t d dalla ll clausola l l From F o Aggregate. A t Operatori di query LINQ in Visual Basic (1) • Clausola WHERE Facoltativo. Specifica una condizione di filtro per una query. ‘Ritorna tutti i nomi dei prodotti la cui categoria è ‘Beverages’ Dim names = From product In products Where procuct.Category = “Beverages” Select product.Name • Clausola ORDER BY Facoltativo. Specifica l'ordinamento per le colonne in una query. y ‘Ritorna una lista di libri ordinati per prezzo in ordine ascendente Dim titlesAscendingPrice g = From b In Books Order By b.Price Operatori di query LINQ in Visual Basic (2) • Clausola JOIN Facoltativo. Combina due insiemi in un unico insieme. ‘Ritorna un insieme combinato di tutti i processi e delle corrispondenti descrizioni Dim processes = From proc In Process.GetProcesses Join desc In processDescriptions On proc.ProcessName Equals desc.ProcessName Select proc.ProcessName, proc.Id, desc.Description • Clausola GROUP BY Facoltativo. Raggruppa gli elementi di un risultato della query. ‘Ritorna una lista di ordini raggruppati secondo la data dell’ordine e ordinati in ordine ascendente Di orders Dim d = F From order d I orderList In d Li t Order By order.OrderDate Group By OrderDate = order.OrderDate Into OrdersByDate = Group Operatori di query LINQ in Visual Basic (3) • Clausola GROUP JOIN Facoltativo. Combina due insiemi in un unico insieme gerarchico. ‘Ritorna una collezione combinata di clienti e ordini Di customerList Dim t Li t = From F cust t In I customers t Group Join ord In orders On cust.CustomerID Equals ord.CustomerID Into CustomerOrders = Group, Group OrderTotal = Sum(ord.Total) Select cust.CompanyName, cust.CustomerID, CustomerOrders OrderTotal CustomerOrders, Operatori di query LINQ in Visual Basic (4) • Clausola AGGREGATE Una clausola Aggregate applica una o più funzioni di aggregazione a un insieme. Ad esempio, è possibile utilizzare la clausola Aggregate per calcolare una somma di tutti gli elementi restituiti da una query. ‘Ritorna la somma di tutti i totali degli ordini Dim orderTotal= Aggregate gg g order In Orders Into Sum(order.Total) È inoltre possibile utilizzare la clausola Aggregate gg g per modificare una query. y Ad esempio, è possibile utilizzare la clausola Aggregate per eseguire un calcolo su un insieme di query correlato. ‘Ritorna il nome dell’azienda del cliente ed il totale degli ordini più grande per ogni cliente Dim customerMax = From cust In Customers Aggregate order In cust.Orders Into MaxOrder = Max(order.Total) Max(order Total) Select cust.CompanyName, MaxOrder Operatori di query LINQ in Visual Basic (5) • Clausola LET Facoltativo. Calcola un valore e lo assegna a una nuova variabile nella query. ‘Ritorna una lista di prodotti che sono stati scontati del 10% Dim discountedProducts = From p prod In p products Let Discount = prod.UnitPrice * (0.1) Where Discount >= 50 Select prod.ProductName, prod.UnitPrice, Discount • Clausola DISTINCT Facoltativo Limita i valori della variabile di iterazione corrente per eliminare i valori duplicati nei Facoltativo. risultati della query. ‘Ritorna una lista di città senza duplicati Dim cities = From cust In customers Select cust.City Distinct Operatori di query LINQ in Visual Basic (6) • Clausola SKIP Facoltativo. Ignora un numero specificato di elementi in un insieme e quindi restituisce gli Facoltativo elementi rimanenti. ‘Ritorna una lista di clienti. I primi 10 clienti vengono ignorati, mentre vengono ritornati i rimanenti. Dim customerList = From cust In customers Skip 10 • Clausola SKIP WHILE Facoltativo. Ignora gli elementi in un insieme finché la condizione specificata è true e quindi restituisce gli elementi rimanenti. ‘Ritorna una lista di clienti. La query ignora tutti i clienti fino al primo cliente per cui IsSubscriber ritorna falso. Dim customerList = From cust In customers Skip While IsSubscriber(cust) Operatori di query LINQ in Visual Basic (7) • Clausola TAKE Facoltativo. Restituisce un numero specificato di elementi contigui dall'inizio di un insieme. ‘Ritorna i primi 10 clienti Dim customerList = From cust In customers Take 10 • Clausola TAKE WHILE Facoltativo. Include gli elementi in un insieme finché la condizione specificata è true e quindi ignora gli elementi rimanenti. ‘Ritorna una lista di clienti. La query ritorna i clienti fino al primo cliente per cui HasOrders ritorna falso. Quel cliente e tutti i rimanenti vengono ignorati. Dim customersWithOrders = From cust In customers Order By cust.Orders.Count Descending Take While HasOrders(cust) Esecuzione query •Esecuzione posticipata: ‘Origine g dati. Dim numbers() As Integer = {0,1,2,3,4,5,6} ‘Creazione query. Dim evensQuery = From num In numbers Where num Mod 2 = 0 Select Num ‘Esecuzione query che da come risultato valori. From Each h number b In evensQuery Console.Write(number & “ “) Next una sequenza di La query viene creata ma non eseguita immediatamente. La definizione della query viene invece archiviata nella variabile di query evensQuery. La query viene eseguita successivamente, in genere utilizzando un ciclo For Each che restituisce una sequenza di valori o applicando un operatore di query standard, ad esempio Count o Max. Questo processo viene denominato esecuzione posticipata. Esecuzione query (1) ‘Esecuzione query che da come risultato un singolo valore. Dim evens e ens = evensQuery.Count() e ensQ er Co nt() •Esecuzione Esecuzione immediata: Nell'esecuzione immediata la query viene eseguita al momento della definizione. L'esecuzione viene attivata quando si applica un metodo che richiede l'accesso ai singoli elementi del risultato della query. query L L'esecuzione esecuzione immediata viene spesso forzata utilizzando uno degli operatori di query standard che restituiscono singoli valori, ad esempio Count, Max, Average e First. Dim numEvens = (From num In numbers Where num Mod 2 = 0 Select num).Count() LINQ to SQL Utilizzando LINQ to SQL è possibile avvalersi della tecnologia LINQ per accedere ai database SQL come se si trattasse di un insieme in memoria • Operazioni eseguibili con LINQ to SQL È possibile creare query per ottenere informazioni ed eseguire operazioni di inserimento, aggiornamento ed eliminazione di dati dalle tabelle. • Selezione S l i Per eseguire una selezione (proiezione) è sufficiente scrivere una query LINQ nel proprio linguaggio di programmazione e successivamente eseguirla per recuperare i risultati. In LINQ to SQL tutte le operazioni indispensabili vengono convertite nelle operazioni SQL necessarie con SQL, cui si ha dimestichezza. • Inserimento Per eseguire un'operazione Insert SQL, aggiungere semplicemente oggetti al modello a oggetti creato e chiamare SubmitChanges su DataContext. Nell'esempio seguente vengono aggiunti un nuovo cliente e informazioni sul cliente alla tabella Customers utilizzando InsertOnSubmit. LINQ to SQL (1) Dim nw As New Northwnd(“C:\northwnd.mdf”) Dim cust As New Customer With {.CompanyName = “SomeCompany”, .City = “London”, .CustomerID C = 98128, .PostalCode = 55555, .Phone = “555-555-5555”} nw.Customers.InsertOnSubmit(cust) nw.SubmitChanges() • Aggiornamento Per eseguire un'operazione Update per una voce del database, bisogna recuperare innanzitutto tale voce e modificarla direttamente nel modello a oggetti. gg Dopo p avere modificato l'oggetto, gg , chiamare SubmitChanges su DataContext per aggiornare il database. Nell'esempio seguente vengono recuperati tutti i clienti dell'area londinese. Il nome della città viene quindi modificato da "London" in "London - Metro", infine viene chiamato SubmitChanges per inviare le modifiche al database. database Dim nw As New Northwnd(“C:\northwnd.mdf”) Dim cityNameQuery = From cust In nw.Customers Where h cust.City.Contains(“London”) Ci C i “ d ” Select cust For Each customers In cityNameQuery If customer.City = “London” Then Customer.City = “London – Metro” End If Next nw.SubmitChanges() LINQ to SQL (2) • Eliminazione Per eseguire un'operazione Delete per un elemento, elemento rimuovere l'elemento dall'insieme al quale appartiene, quindi chiamare SubmitChanges su DataContext per eseguire il commit della modifica. Nell'esempio p seguente g il cliente con il codice CustomerID 98128 viene recuperato p dal database. Quindi, dopo la conferma che la riga del cliente è stata recuperata, viene chiamato DeleteOnSubmit per rimuovere quell'oggetto dall'insieme. Infine viene chiamato SubmitChanges per inoltrare l'operazione di eliminazione al database. Dim nw As New Northwnd(“C:\northwnd.mdf”) Dim deleteIndivCust = From cust In nw.Customers Where cust.customerID = 98128 Select cust If deleteIndivCust.Count > 0 Then nw.Customers.DeleteOnSubmit(deleteIndivCust.First) nw.SubmitChanges() End If LINQ to DataSet Dataset è uno dei componenti più utilizzati di ADO.NET. Rappresenta un elemento chiave del modello di programmazione disconnesso su cui si basa ADO.NET ADO NET e consente di memorizzare in modo esplicito nella cache dati di origini dati diverse. Una tecnica comune utilizzata per ridurre il numero di richieste su un database consiste nell'utilizzare DataSet per la memorizzazione nella cache di livello intermedio. Esempio: Si consideri un'applicazione Web ASP.NET basata sui dati. Spesso, una parte significativa dei dati dell'applicazione non viene modificata frequentemente ed è comune a più sessioni o utenti Tali dati possono essere mantenuti in memoria sul server Web, utenti. Web in modo da ridurre il numero di richieste al database e velocizzare le interazioni utente. Un altro aspetto utile di DataSet è che consente a un'applicazione di portare nello spazio dell'applicazione sottoinsiemi di dati da una o più origini dati. L'applicazione può quindi modificare i dati in memoria, mantenendo comunque la propria forma relazionale. LINQ to DataSet (1) DataSet dispone di funzionalità limitate di query Con LINQ to DataSet è più facile e veloce eseguire una query su dati memorizzati nella cache di un oggetto dataset. dataset Queste query sono espresse nel linguaggio di programmazione stesso, anziché come valori letterali stringa incorporati nel codice dell'applicazione. Gli sviluppatori non devono pertanto imparare un diverso linguaggio di query. Prima di poter eseguire query su un oggetto dataset con LINQ to DataSet, DataSet è necessario popolare DataSet. È possibile caricare dati in un oggetto DataSet in diversi modi, ad esempio utilizzando la classe DataAdapter o LINQ to SQL. È possibile iniziare a eseguire query su un oggetto DataSet dopo avervi caricato i dati. La formulazione di query con LINQ Q to DataSet S è simile all'utilizzo di LINQ Q (Language-Integrated ( Q Query) ) su altre origini dati con supporto LINQ. È possibile eseguire query LINQ su singole tabelle di un DataSet o su più di una tabella utilizzando gli operatori di query standard Join e GroupJoin. LINQ to DataSet (2) • Applicazioni a più livelli Le applicazioni dati a più livelli sono applicazioni mirate ai dati separate in più livelli logici. Una tipica applicazione a più livelli include un livello di presentazione, un livello intermedio e un livello dati. La separazione dei componenti dell'applicazione in livelli aumenta la gestibilità tibilità e la l scalabilità l bilità dell'applicazione. d ll' li i In che modo LINQ to DataSet si correla a dataset e si inserisce in un'applicazione a più livelli? Esecuzione di una query su DataSet • Query su una singola tabella Le query LINQ possono essere eseguite su origini dati che implementano ll'interfaccia interfaccia IEnumerable o l'interfaccia IQueryable. Poiché la classe DataTable non implementa nessuna di queste due interfacce, è necessario chiamare il metodo AsEnumerable se si desidera utilizzare DataTable come origine nella clausola From della query LINQ. Dim ds As New DataSet() ds.Locale = CultureInfo.InvariantCulture ‘Utilizzare il metodo FillDataSet per caricare i dati nel dataset FillDataSet(ds) Dim orders As DataTable = ds.Tables( ds Tables(“SalesOrderHeader”) SalesOrderHeader ) Dim query = From order In orders.AsEnumerable() Where order.Field(Of Boolean)(“OnlineOrderFlag”) = True Select New With{ .SalesOrderID = order.Field(Of Integer)(“SalesOrderID”), .OrderDate = order.Field(Of Datetime)(“OrderDate”), .SalesOrderNumber = order.Field(Of String)(“SalesOrderNumber”) } Esecuzione di una query su DataSet (1) For Each onlineOrder In query Console Write("Order ID: " & onlineOrder.SalesOrderID) Console.Write("Order onlineOrder SalesOrderID) Console.Write(" Order date: " & onlineOrder.OrderDate) Console.WriteLine(" Order number: " & onlineOrder.SalesOrderNumber) Next • Vengono utilizzati due operatori di query standard: Where e Select. • La clausola Where filtra la sequenza in base a una condizione, in questo caso che OnlineOrderFlag sia impostato su true. true • L'operatore Select alloca e restituisce un oggetto enumerabile che acquisisce gli argomenti passati all'operatore. Nell'esempio viene creato un tipo anonimo con tre proprietà: SalesOrderID, OrderDate e SalesOrderNumber. • I valori di queste tre proprietà vengono impostati sui valori delle colonne SalesOrderID, SalesOrderID OrderDate e SalesOrderNumber della tabella SalesOrderHeader. • Il ciclo Foreach enumera quindi gli oggetti enumerabili restituiti da Select e produce i risultati della query. • Poiché la query è un tipo Enumerable la valutazione della query viene posticipata finché non viene eseguita un'iterazione della variabile di query in un ciclo Foreach. LINQ to Objects Il termine LINQ to Objects si riferisce all'utilizzo diretto delle query LINQ con un insieme Ienumerable o IEnumerable(T), IEnumerable(T) senza ll'utilizzo utilizzo di un provider LINQ intermedio o di un un'API API come LINQ to SQL o LINQ to XML. È possibile utilizzare LINQ per eseguire una query su qualsiasi insieme enumerabile come List(T), Array,etc. Procedura: eseguire una query su un ArrayList con LINQ Quando si utilizza LINQ per eseguire g una queryy su insiemi Ienumerable non g generici, ad esempio ArrayList, è necessario dichiarare in modo esplicito il tipo della variabile di intervallo per riflettere il tipo specifico degli oggetti nell'insieme. Ad esempio, se si ha un oggetto ArrayList di oggetti Student la Clausola From deve essere analoga a quella riportata di seguito: Dim query = From student As Student In arrList … Specificando il tipo della variabile di intervallo, viene eseguito il cast di ogni elemento presente nell'oggetto ArrayList su un oggetto Student. LINQ to Objects (1) Nell'esempio seguente viene illustrata una query semplice su un oggetto ArrayList. In questo esempio vengono utilizzati gli inizializzatori di oggetto quando il codice chiama il metodo Add, sebbene non sia un requisito. Imports System.Collections Imports System.Ling Module Module1 Public Class Student Public FirstName As String Public LastName As String Public Scores As Integer() End Class Sub Main() Dim student1 As New Student With {.FirstName = “Svetlana”, .LastName = “Omelchenko”, .Scores Scores = New Integer() {98,92,81,60}} {98 92 81 60}} Dim student2 As New Student With {.FirstName = “Claire”, .LastName = “O’Donnell”, .Scores = New Integer() {75,84,91,39}} Dim student3 As New Student With {.FirstName { FirstName = “Cesar” Cesar , .LastName = “Garcia”, .Scores = New Integer() {97,89,85,82}} LINQ to Objects (2) Dim student4 As New Student With {.FirstName = “Sven”, .LastName = “Mortensen”, .Scores = New Integer() {88,94,65,91}} Dim arrList As New ArrayList() arrList.Add(student1) arrList.Add(student2) arrList.Add(student3) arrList.Add(student4) Dim query = From student As Student In arrList Where student.Scores(0) > 95 Select student For Each student As Student In query Console.Writeline(student.LastName & “: ” & student.Scores(0)) Next Console.Writeline(“Press any key to exit.”) Console.ReadKey() End Sub ‘Output: ‘ Omelchenko: 98 ‘ Garcia: 97 LINQ to XML LINQ to XML è un'interfaccia di programmazione g XML in memoria con supporto LINQ che consente di utilizzare codice XML dall'interno dei linguaggi di programmazione di .NET Framework. È simile al modello DOM (Document Object Model) in quanto porta in memoria il documento XML. È quindi possibile eseguire query e modificare il documento e dopo averlo modificato salvarlo in un file o serializzarlo e inviarlo tramite rete. LINQ to XML è tuttavia diverso da DOM. Fornisce infatti un nuovo modello a oggetti, più leggero e facile da utilizzare, che sfrutta i miglioramenti apportati al linguaggio in Visual C# 2008. Il principale vantaggio di LINQ to XML è costituito dall'integrazione con LINQ (LanguageIntegrated Query). Query) Grazie a tale integrazione è possibile scrivere query sul documento XML in memoria per recuperare insiemi di elementi e di attributi. La funzionalità LINQ Q di LINQ Q to XML consente di eseguire g query q y su codice XML. Ad esempio, si supponga di avere un tipico ordine di acquisto in formato XML come descritto nel file XML di esempio: Purchase Order.xml. Utilizzando LINQ to XML, è possibile eseguire la query seguente per ottenere il valore dell'attributo relativo al numero di parte di ciascun articolo ti l incluso i l d ll' di di acquisto: dell'ordine i t LINQ to XML (1) PurchaseOrder.xml: <?xml version="1.0"?> <PurchaseOrder PurchaseOrderNumber="99503“ OrderDate="1999-10-20"> <Address Type="Shipping"> <Name>Ellen Adams</Name> <Street>123 Maple Street</Street> <City>Mill Valley</City> <State>CA</State> <Zip>10999</Zip> p p <Country>USA</Country> </Address> <Address Type="Billing"> yp g <Name>Tai Yee</Name> <Street>8 Oak Avenue</Street> <City>Old Town</City> <State>PA</State> <Zip>95819</Zip> <Country>USA</Country> </Address> <DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes> ………… LINQ to XML (2) ……… <Items> <Item It PartNumber="872-AA"> P tN b AA <ProductName>Lawnmower</ProductName> <Quantity>1</Quantity> <USPrice>148.95</USPrice> <Comment>Confirm C t C fi thi is this i electric</Comment> l t i C t </Item> <Item PartNumber="926-AA"> <ProductName>Baby P d tN B b M Monitor</ProductName> it P d tN <Quantity>2</Quantity> <USPrice>39.98</USPrice> <ShipDate>1999-05-21</ShipDate> </Item> /It </Items> </PurchaseOrder> Query in Visual Basic: Dim partNos = From item In purchaseOrder...<Item> Select item.@PartNumber LINQ to XML (3) Oltre alle funzionalità LINQ di LINQ to XML, è importante sottolineare che LINQ to XML include un un'interfaccia interfaccia di programmazione XML migliorata. LINQ to XML consente di eseguire tutte le operazioni necessarie quando si programma con XML, tra cui: caricare codice XML da file o flussi; serializzare i li codice di XML in i file fil o flussi; fl i creare codice XML nuovo utilizzando la costruzione funzionale; modificare la struttura ad albero XML in memoria utilizzando metodi specifici; convalidare strutture ad albero XML utilizzando lo schema XSD; utilizzare una combinazione di queste funzionalità per trasformare strutture ad albero XML da una forma in un’altra. Creazione di strutture ad albero XML Riveste particolare significato la semplicità con cui è possibile creare strutture ad albero XML. In Visual Basic il codice per costruire la struttura ad albero XML prevede l'utilizzo di valori letterali XML: LINQ to XML (4) Dim contacts = <Contacts> <Contact> <Name>Patrick Hines</Name> <Phone Type="Home">206-555-0144</Phone> <Phone Type="Work">425-555-0145</Phone> <Address> <Street1>123 Main St</Street1> <City>Mercer Island</City> <State>WA</State> <Postal>68042</Postal> </Address> </Contact> </Contacts> Il compilatore Visual Basic traduce i valori letterali XML in chiamate al metodo in LINQ to XML.