È uno dei tre nuovi controlli aggiunti alla versione 3.5 dell'engine di ASP.NET (assieme a ListView
e DataPage
) ed è il nuovo arrivato nella famiglia dei controlli di tipo Data-Source. In particolare, questo controllo permette agli sviluppatori Web, di sfruttare con sforzo minimo tutti vantaggi offerti da LINQ.
Possiamo specificare una query di selezione utilizzando oggetti in memoria, svincolati dal classico SQL. L'utilizzo di questo controllo va a braccetto con l'uso di LINQ to SQL o del nuovo Entity Framework. Entrambi permettono la mappatura della base di dati su classi .NET.
Il funzionamento del controllo si basa su una particolare classe, detta classe di "contesto", o più comunemente DataContext: LinqDataSource
si preoccupa di creare le istanze di DataContext
per utilizzarle come fonte di dati per le proprie operazioni.
Quindi se utilizziamo le classi generate da Visual Studio a fronte dell'utilizzo di LINQ to SQL o dell'Entity Framework, sicuramente troviamo la classe DataContext
già presente nel progetto. In questo caso possiamo specificarne il tipo all'interno di un'istanza del controllo LinqDataSource
.
Vedremo però che l'utilizzo di LinqDataSource
non è limitato a lavorare solamente con classi LINQ to SQL, ma con tutte le classi .NET che espongono collezioni di oggetti (o array) come proprietà pubbliche.
Possiamo considerare LinqDataSource
come la fusione tra il controllo SqlDataSource
e il controllo ObjectDataSource
, in quanto come il primo permette di specificare la query di selezione direttamente nel markup della pagina, senza però fare alcun riferimento alla connessione con il DB; la caratteristica comune con l'ObjectDataSource
è, invece, quella di permettere la selezione di informazioni da un livello di classi che si interpone tra la User Interface e il livello di accesso ai dati, con il vantaggio di non dover modificare l'implementazione dei metodi di selezione a fronte di un cambiamento anche minimo della query.
L'utilizzo del controllo si basa principalmente su queste quattro proprietà:
- ContextTypeName - indica il tipo che verrà istanziato in automatico ed utilizzato come fonte di dati per le nostre query LINQ
- TableName - indica il nome della tabella a cui collegarsi e da cui prelevare eventualmente informazioni
- Select - permette di indicare le colonne (o proprietà) interessate dalla query
- Where - permette applicare filtri su colonne o proprietà relative al tipo specificato nella proprietà
TableName
Risulta abbastanza intuitivo assegnare alla proprietà ContextTypeName
la classe DataContext
generata dalla mappatura LINQ to SQL, ma possiamo anche astrarci da questa concezione e pensare tale proprietà come la classe che espone i nostri dati, qualsiasi essi siano.
Possiamo utilizzare ad esempio una classe "manager" come classe di contesto, che espone una proprietà pubblica di tipo Array
.
public class EnumManager { public EnumManager() { } public Array Values { get { return Enum.GetValues(typeof(City)); } } } public enum City { London, Milan, NewYork }
Vediamo come utilizzare un controllo di tipo LinqDataSource
ed effettuare il binding dei valori di un'enumerazione su una DropDownList
con due linee di codice:
<asp:LinqDataSource ID="linqDSEnum" runat="server" ContextTypeName="EnumManager" TableName="Values" /> <asp:DropDownList ID="ddlEnum" runat="server" DataSourceID="linqDSEnum" />
Allo stesso modo possiamo utilizzare degli oggetti custom (magari facenti parte del modello ad oggetti della nostra applicazione), esposti attraverso una collezione, ed utilizzare la clausola SELECT
per modificare la struttura dei risultati di default:
public class Manager { public Manager() {} public Manager(string name, string surname, int age) { Name = name; Surname = surname; Age = age; } public string Name { get; set; } public string Surname { get; set; } public int Age { get; set; } } public class Managers { private List<Manager> list; public Managers() { list = new List<Manager>(); list.Add(new Manager("Giuseppe", "Marchi", 25)); list.Add(new Manager("Andrea", "Marzilli", 18)); } public List<Manager> List { get { return list; } } }
Default.aspx
<asp:LinqDataSource ID="linqDSList" runat="server" ContextTypeName="Managers" TableName="List" Select="Name + ' ' + Surname" /> <asp:DropDownList ID="ddlList" runat="server" DataSourceID="linqDSList" />
Nota: se non viene specificata la clausola SELECT
, come nell'esempio precedente, LinqDataSource
effettua una query prelevando tutte le proprietà pubbliche disponibili, come se eseguisse "SELECT * FROM ...
"
In questo caso specifico, abbiamo utilizzato la sintassi LINQ per concatenare due colonne del set di risultati (che sarebbero due proprietà della classe Manager
) in modo tale da formare una nuova colonna risultante dalle due, eliminando dalla visualizzazione finale la proprietà Age
.
Altra cosa molto importante per comprendere a pieno il funzionamento di LinqDataSource
è il codice contenuto nel costruttore della classe Managers
, dove viene istanziata la collezione di oggetti di tipo Manager
e ne vengono caricati al suo interno un paio di prova.
Il funzionamento di questo esempio ci serve quindi a capire che prima di effettuare la query di selezione sulla fonte di dati scelta, il controllo crea una nuova istanza del tipo espresso all'interno della proprietà DataContextName
in automatico per poi passare alla selezione vera e propria.
In coppia con LINQ to SQL
Come abbiamo detto nella prima parte dell'articolo, l'utilizzo di controlli LinqDataSource
assieme ad un modello ad oggetti creato attraverso LINQ to SQL è sicuramente la tecnica più spontanea da utilizzare, data la presenza di una classe di "contesto" (il DataContext
) auto-generata.
Per fare un semplice esempio, esaminiamo il codice necessario ad effettuare una selezione su una tabella (utilizziamo il database d'esempio Northwind), e per collegare i risultati della query ad una DropDownList
:
<asp:LinqDataSource ID="linqDSNortwind1" runat="server" ContextTypeName="NorthwindDataContext" TableName="Customers" Select="City" OrderBy="City DESC" /> <asp:DropDownList ID="ddlCity1" runat="server" DataSourceID="linqDSNortwind1" />
Possiamo fare tutto tramite markup, senza scrivere nemmeno una riga di codice: la facilità di utilizzo del controllo è disarmante, soprattutto considerando la quantità di operazioni possibili sui dati.
Nell'esempio, abbiamo una query che seleziona tutte le città presenti all'interno della tabella Customers
, in ordine alfabetico. L'equivalente scritto in SQL e utilizzando le classi del namespace System.Data.SqlClient
, richiede un tempo di sviluppo nettamente superiore.
Esistono molti casi però, in cui l'utilizzo delle proprietà Select
, Where
e OrderBy
non bastano ad esprimere la query che abbiamo in mente (ad esempio query contenenti una o più JOIN
, sotto query, clausole distinct
o operazioni sulle colonne). Questo perché, tramite quelle proprietà, ci perdiamo la stragrande maggioranza delle funzionalità esposte in maniera nativa da LINQ
.
Non siamo comunque completamente privati dell'utilizzo della sintassi LINQ, possiamo avvalercene modificando la sequenza delle operazioni di base eseguite dal controllo LinqDataSource
. Tale modifica consiste nella gestione di uno o più eventi esposti dal controllo. Esaminiamone i più importanti.
Eventi | Descrizione |
---|---|
ContextCreting ContextCreated ContextDisposing |
eventi per la gestione personalizzata della classe di "contesto" |
Selecting Selected |
eventi pre e post selezione |
Inserting Inserted |
eventi pre e post inserimento |
Updating Updated |
eventi pre e post aggiornamento |
Deleting Deleted |
eventi pre e post cancellazione |
Nel nostro caso, attraverso la gestione dell'evento Selecting
, possiamo scrivere la nostra query LINQ da zero e sfruttare tutta la potenza del linguaggio di selezione.
Default.aspx
<asp:LinqDataSource ID="linqDSNortwind2" runat="server" ContextTypeName="NorthwindDataContext" TableName="Customers" onselecting="linqDSNortwind2_Selecting" /> <asp:DropDownList ID="ddlCity2" runat="server" DataSourceID="linqDSNortwind2" />
Default.aspx.cs
public partial class _Default : System.Web.UI.Page { private NorthwindDataContext context; protected void Page_Init(object sender, EventArgs e) { context = new NorthwindDataContext(); } protected void linqDSNortwind2_Selecting(object sender, LinqDataSourceSelectEventArgs e) { var cities = (from c in context.Customers select c.City).Distinct(); e.Result = cities; } }
Allo stesso modo poi, possiamo utilizzare il metodo ContextCreated
per applicare alla nostra selezione di base un meccanismo di prefetch, cioè la tecnica di pre-popolamento di dati tra loro correlati mediante un vincolo di chiave esterna.
protected void linqDSLaxy2_ContextCreated(object sender, LinqDataSourceStatusEventArgs e) { DataLoadOptions options = new DataLoadOptions(); options.LoadWith(o => o.Customer); (e.Result as NorthwindDataContext).LoadOptions = options; }
<asp:LinqDataSource ID="linqDSLazy1" runat="server" ContextTypeName="NorthwindDataContext" TableName="Customers" /> <asp:DropDownList ID="ddlLazy" runat="server" DataSourceID="linqDSLazy1" DataTextField="ContactName" DataValueField="CustomerID" AutoPostBack="true" /><br /> <asp:LinqDataSource ID="linqDSLaxy2" runat="server" oncontextcreated="linqDSLaxy2_ContextCreated" ContextTypeName="NorthwindDataContext" TableName="Orders" Select="new (Customer.ContactName, CustomerID, OrderID, OrderDate)" Where="CustomerID == @CustomerID" > <WhereParameters> <asp:ControlParameter ControlID="ddlLazy" Name="CustomerID" PropertyName="SelectedValue" Type="String" /> </WhereParameters> </asp:LinqDataSource> <asp:GridView ID="gridLazy" runat="server" DataSourceID="linqDSLaxy2" DataKeyNames="CustomerID" />
Nell'esempio abbiamo pre-popolato la nostra lista di Ordini
, con tutte le informazioni riguardanti il cliente che è legato tramite il vincolo di chiave esterna posto sulla colonna CustomerID
. In fase poi di selezione, all'interno della proprietà Select
di LinqDataSource
, abbiamo potuto modificare il set dei risultati includendo, oltre alle informazioni di base di un ordine, anche quelle del relativo cliente, attraverso l'implementazione del meccanismo del lazy loading offerto da LINQ, sfruttando la sintassi <<Entità.Colonna>>
(es: Customer.ContactName
).
Aggiornamenti, inserimenti e cancellazioni
Come per i controlli SqlDataSource
e ObjectDataSource
, anche LinqDataSource
, se collegato ad una classe DataContext
generata da un modello di mapping LINQ to SQL, ha la possibilità di abilitare le tipiche operazioni di inserimento, modifica e cancellazione di dati da una tabella.
Per farlo, basta impostare a true
le proprietà:
- EnableInsert
- EnableUpdate
- EnableDelete
e, chiaramente, legare il data-source ad un controllo di visualizzazione che gestisca queste tre operazioni (come per esempio una DetailsView
).
<asp:LinqDataSource ID="linqDSCustomers" runat="server" ContextTypeName="NorthwindDataContext" TableName="Customers" OrderBy="ContactName" /> <asp:DropDownList ID="ddlCustomers" runat="server" DataSourceID="linqDSCustomers" DataValueField="CustomerID" DataTextField="ContactName" AutoPostBack="true" /><br /> <asp:LinqDataSource ID="linqDSUpdate" runat="server" ContextTypeName="NorthwindDataContext" TableName="Customers" Where="CustomerID = @CustomerID" EnableUpdate="True" EnableDelete="true" EnableInsert="true"> <WhereParameters> <asp:ControlParameter ControlID="ddlCustomers" Name="CustomerID" PropertyName="SelectedValue" /> </WhereParameters> </asp:LinqDataSource> <asp:DetailsView ID="DetailsView1" runat="server" DataSourceID="linqDSUpdate" DataKeyNames="CustomerID" AutoGenerateEditButton="True" AutoGenerateDeleteButton="true" AutoGenerateInsertButton="true" />
Anche in questo abbiamo raggiungere il nostro scopo scrivere code-behind: è il controllo LinqDataSource
a preoccuparsi di generare le query di update, insert e delete in LINQ.
Infine, come già evidenziato nel corso dell'articolo, c'è un elenco di parametri diponibili per le varie clausole del controllo (select
, where
, orderby
, groupby
, etc.). Questi parametri rappresentano le stesse funzioni degli omologhi per i controlli SqlDataSource
e ObjectDataSource
.
Conclusioni
Avere padronanza di questo tipo di controllo può risultare utile, in quanto è possibile creare semplici applicazioni di interazione con una base di dati, senza scrivere pagine e pagine di codice .NET. Inoltre, l'utilizzo di questo controllo, implica la presenza di un modello ad oggetti ben strutturato, anche se inserire la logica di selezione direttamente all'interno della parte dichiarativa di una pagina ASP.NET non è proprio una "best-practice".