- LINQ Prof. Mauro Giacomini Dr.ssa Simona Bertolini Ing. Susanna Pivetti Anno Accademico: 2009-2010 LINQ: Language-Integrated Query 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 l'esecuzione di query e l'aggiornamento dei dati, consentendo l'estensione della tecnologia per supportare praticamente qualsiasi tipo di archivio dati. Introduzione a LINQ in Visual Basic Una query è un'espressione che recupera dati da un'origine 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 XQuery per XML. Lo sviluppatore di applicazioni 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. Cos’è LINQ? Uno strumento per interrogare in modo uniforme: • Oggetti in memoria LINQ to Objects / DataSet • Database relazionali LINQ to SQL • Documenti XML LINQ to XML • Modelli E-R LINQ to Entities Cosa serve? .NET 3.5 / Visual Studio 2008 C# 3 VB 3 Metodi di accesso a Database Noi considereremo come esempio ADO.NET, ma le metodologie sono applicabili a tutti i metodi. Query • Origine dei dati LINQ consente di eseguire query su dati in un database SQL Server, XML, matrici e insiemi in memoria, dataset ADO.NET o qualsiasi altra origine dati remota o locale che supporta LINQ. È possibile fare tutto questo con i comuni elementi del linguaggio Visual Basic. Poiché le query sono scritte in Visual Basic, i risultati vengono restituiti come oggetti fortemente tipizzati, che supportano l’IntelliSense (strumento 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 Una query può essere eseguita appena definita (esecuzione immediata) oppure è possibile archiviare la definizione ed eseguire la query in un secondo momento (esecuzione posticipata). Fasi di un’operazione di query - esempio ‘ Creazione della sorgente dei dati Dim db = New Northwind("c:\northwind\northwnd.mdf") ‘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 Robustezza della query con LINQ Provider LINQ • Un provider LINQ Visual Basic esegue il mapping di query LINQ sull'origine dati su cui eseguire una 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 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 Vantaggi di LINQ Uniformità ed estensibilità Query a livello di linguaggio Type checking Intellisense Protezione dall’SQL Injection Operatori di query LINQ in Visual Basic • Clausola FROM Per iniziare una query è obbligatoria una clausola From o una clausola di aggregazione. Le funzioni di aggregazione in SQL ritornano un valore singolo calcolato dai valori di una colonna. 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 = “IT” Select cust.Companyname ‘Ritorna il nome dell’azienda per tutti i clienti il cui stato è uguale a ‘IT’ • Clausola SELECT Facoltativo. Dichiara un insieme di variabili di iterazione per una 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 variabili di iterazione specificate dalla clausola From o Aggregate. 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 è ‘Food’ Dim names = From product In products Where product.Category = “Food” Select product.Name • Clausola ORDER BY Facoltativo. Specifica l'ordinamento per le colonne in una query. ‘Ritorna una lista di libri ordinati per prezzo in ordine ascendente Dim titlesAscendingPrice = From b In Books Order By b.Price Operatori di query LINQ in Visual Basic (2) • 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 order In Orders Into Sum(order.Total) È inoltre possibile utilizzare la clausola Aggregate per modificare una query. 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) Select cust.CompanyName, MaxOrder Operatori di query LINQ in Visual Basic (3) • 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 libri raggruppati secondo la data di pubblicazione in ordine ascendente Dim books = From book In bookList Order By book.PublicationDate Group By PublicationDate = book.PublicationDate Into BooksByDate = Group Operatori di query LINQ in Visual Basic (4) • Clausola GROUP JOIN Facoltativo. Combina due insiemi in un unico insieme gerarchico. ‘Ritorna una collezione combinata di clienti e ordini Dim customerList = From cust In customers Group Join ord In orders On Join tra cust e order per CustomerID cust.CustomerID Equals ord.CustomerID Into CustomerOrders = Group, Diamo il nome al gruppo e poi aggiungiamo una funzione di OrderTotal = Sum(ord.Total) aggregazione per calcolare il totale Select cust.CompanyName, cust.CustomerID, degli ordini di un cliente CustomerOrders, OrderTotal 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 prod In 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 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 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 IsMale ritorna falso: quindi ignora tutti i clienti fino alla prima cliente femmina. Dim customerList = From cust In customers Skip While IsMale(cust) ‘IsMale è una funzione che definisco nel mio codice VB. Queste funzioni vengono dette Extension Methods 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) Tipi di esecuzione query •Esecuzione posticipata – fase 1: ‘Origine dati: oggetto in memoria. 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.Num Esecuzione query •Esecuzione posticipata – fase 2: ‘Esecuzione query che dà come risultato valori. For Each number 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 che dà come risultato un singolo valore. Dim evens = evensQuery.Count() Esecuzione query (1) •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. L'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 • Provider per accedere a DB relazionali. • Da Visual Studio basta un drag-and-drop per ottenere classi che corrispondono alle tabelle con le loro relazioni. LINQ to SQL (1) 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 Per eseguire una selezione (proiezione) è sufficiente scrivere una query LINQ nel proprio linguaggio di programmazione (VB o C#) e successivamente eseguirla per recuperare i risultati. In LINQ to SQL tutte le operazioni indispensabili vengono convertite nelle operazioni SQL necessarie. (Esempi precedenti) LINQ to SQL (2) • Inserimento Per eseguire un'operazione Insert SQL, aggiungere semplicemente oggetti al modello a oggetti creato e chiamare SubmitChanges sul DataContext. Nell'esempio seguente vengono aggiunti un nuovo cliente e informazioni sul cliente alla tabella Customers utilizzando InsertOnSubmit. Dim nw As New Northwnd(“C:\northwnd.mdf”) Dim cust As New Customer With {.CompanyName = “SomeCompany”, .City = “London”, .CustomerID = 98128, .PostalCode = 55555, .Phone = “555-555-5555”} nw.Customers.InsertOnSubmit(cust) nw.SubmitChanges() LINQ to SQL (3) • Aggiornamento Per eseguire un'operazione Update per una voce del database, bisogna recuperare innanzitutto tale voce e modificarla direttamente nel modello a oggetti. Dopo avere modificato l'oggetto, chiamare SubmitChanges sul 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. Dim nw As New Northwnd(“C:\northwnd.mdf”) Dim cityNameQuery = From cust In nw.Customers Where cust.City.Contains(“London”) Select cust For Each customers In cityNameQuery If customer.City = “London” Then Customer.City = “London – Metro” End If Next nw.SubmitChanges() LINQ to SQL (4) • Eliminazione Per eseguire un'operazione Delete per un elemento, rimuovere l'elemento dall'insieme al quale appartiene, quindi chiamare SubmitChanges sul DataContext per eseguire la modifica. Nell'esempio seguente il cliente con il codice CustomerID=98128 viene recuperato 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 e consente di memorizzare in modo esplicito nella cache dati provenienti da 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, 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. LINQ to DataSet (1) L’oggetto 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. 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, è necessario popolare il 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 to DataSet è simile all'utilizzo di LINQ (Language-Integrated 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à e la scalabilità dell'applicazione. In che modo LINQ to DataSet si correla a dataset e si inserisce in un'applicazione a più livelli? L’uso dei DataSet permette di scrivere applicazioni su più livelli, in quanto fa sì che i dati e la presentazione degli stessi stiano su livelli separati. LINQ to DataSet si inserisce tra il livello intermedio (DataSet) e la presentazione dei dati (interfaccia utente). Esecuzione di una query su DataSet • Query su una singola tabella Le query LINQ possono essere eseguite su origini dati di tipo enumerabili (IEnumerable). Poiché la classe DataTable non è enumerabile, è necessario chiamare il metodo AsEnumerable per convertirla in IEnumeable ed utilizzarla come origine nella clausola From della query LINQ. Dim ds As New DataSet() ‘Utilizzare il metodo FillDataSet per caricare i dati nel dataset ‘FillDataSet è un metodo scritto dal programmatore in cui viene ‘riempito il dataset con la tecnica preferita FillDataSet(ds) Dim orders As DataTable = ds.Tables(“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 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. • 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, OrderDate e SalesOrderNumber della tabella SalesOrderHeader. • Il ciclo For Each 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 For Each. LINQ to Objects Il termine LINQ to Objects si riferisce all'utilizzo diretto delle query LINQ con un insieme IEnumerable, senza l'utilizzo di un provider LINQ intermedio o di un'API (Application Programming Interface) come LINQ to SQL o LINQ to XML. È possibile utilizzare LINQ per eseguire una query su qualsiasi insieme enumerabile come liste, array, ecc... Procedura: eseguire una query su un ArrayList con LINQ Quando si utilizza LINQ per eseguire una query su insiemi IEnumerable non generici è necessario dichiarare in modo esplicito il tipo della variabile. Ad esempio, se si ha un oggetto ArrayList di oggetti Student la Clausola From deve essere: Dim query = From student As Student In arrList … Specificando il tipo della variabile, 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 richiesto. Imports System.Collections Imports System.Link Public Class Student Public FirstName As String Public LastName As String Public Scores As Integer() End Class Sub Main() ‘Creo 3 nuove istanze della classe Student (3 studenti) e ne inizializzo i ‘metodi Dim student1 As New Student With {.FirstName = “Mario”, .LastName = “Rossi”, .Scores = New Integer() {98,92,81,60}} Dim student2 As New Student With {.FirstName = “Franco”, .LastName = “Bianchi”, .Scores = New Integer() {75,84,91,39}} Dim student3 As New Student With {.FirstName = “Cesare”, .LastName = “Garcia”, .Scores = New Integer() {97,89,85,82}} LINQ to Objects (2) Dim arrList As New ArrayList() arrList.Add(student1) arrList.Add(student2) arrList.Add(student3) 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 ‘L’output di questo codice sarà: ‘ Rossi: 98 ‘ Garcia: 97 LINQ to XML LINQ to XML è un'interfaccia di programmazione XML in memoria con supporto LINQ che consente di utilizzare codice XML dall'interno dei linguaggi di programmazione del .NET Framework. LINQ to XML porta in memoria il documento XML: è quindi possibile eseguire query direttamente sul file XML e modificare il documento e dopo averlo modificato salvarlo in un file o serializzarlo e inviarlo tramite rete. Inoltre fornisce un nuovo modello a oggetti, leggero e facile da utilizzare. Grazie a LINQ to XML è possibile scrivere query LINQ (Language Integrated Query) sul documento XML in memoria per recuperare insiemi di elementi e di attributi. LINQ to XML (1) Si supponga di avere un ordine di acquisto in formato XML, come descritto nel file XML di esempio: PurchaseOrder.xml. 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> <Country>USA</Country> </Address> <Address Type="Billing"> <Name>Tai Yee</Name> <Street>8 Oak Avenue</Street> <City>Old Town</City> <Country>USA</Country> </Address> <DeliveryNotes>Please leave packages.</DeliveryNotes> LINQ to XML (2) <Items> <Item PartNumber="872-AA"> <ProductName>Lawnmower</ProductName> <Quantity>1</Quantity> <USPrice>148.95</USPrice> <Comment>Confirm this is electric</Comment> </Item> <Item PartNumber="926-AA"> <ProductName>Baby Monitor</ProductName> <Quantity>2</Quantity> <USPrice>39.98</USPrice> <ShipDate>1999-05-21</ShipDate> </Item> </Items> </PurchaseOrder> Query in Visual Basic: utilizzando LINQ to XML, è possibile eseguire la query seguente per ottenere il valore dell'attributo PartNumber di ciascun articolo incluso dell'ordine di acquisto. 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'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 codice XML in file o flussi; 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 (XML Schema Definition); utilizzare una combinazione di queste funzionalità per trasformare strutture ad albero XML da una forma in un’altra. LINQ to XML (4) Creazione di strutture ad albero XML In Visual Basic il codice per costruire la struttura ad albero XML prevede l'utilizzo di valori letterali XML: 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. Conclusioni Abbiamo visto come LINQ sia un componente del .NET Framework di Microsoft che aggiunge ai linguaggi .NET (VB e C#) la possibilità di effettuare interrogazioni su oggetti di vario tipo, utilizzando una sintassi dichiarativa simile a SQL ed unica per ogni tipologia di origine dati. Il provider LINQ viene fornito unitamente al pacchetto Microsoft Visual Studio 2008.