Linq to SQL ed il binding usando il controllo LinqDataSource

In questi giorni, stò implementando una applicazione non eccessivamente complessa in ASP.Net e, dato che non ho vincoli particolari, ho deciso di andare sul nuovo .NET Framework al fine di poter sfruttare tutte le nuove funzionalità (in particolare, Linq to SQL ed AJAX).

Come detto all’inizio, l’applicazione non è complessa ed a questo si aggiunge il pochissimo tempo che ho per realizzarla (e se vogliamo, aggiungiamoci anche che non vedrà mai una versione 2.0 e che, ammesso si dovesse realizzare, non sarò io a farla tongue-out).

Insomma…tutta questa pappardella per dire che, soprattutto in alcune parti dell’applicazione, non stò usando pattern, architetture complesse, SOA, domain model e tutte quelle cose che fanno figo in questo periodo.

Bene…oggi ho praticamente perso quasi 2 ore per venire a capo di un problema.

Se iniziate a guardarvi Linq to SQL, siete sicuramente incappati nella overview a puntate fatta da ScottGu qui. Bene, se arrivate alla quinta puntata, c’è una cosa carinissima (occhio…non ho detto bellissima Wink) che potete fare usando LinqDataSource ed è quella di poter sostituire le foreign-keys della vostra tabella, con valori “leggibili” della tabella padre.

Evito di ripetere le parole di Scott e scippo liberamente un paio di immagini dal suo blog (spero non me ne voglia).

In pratica, partendo da questa situazione:

con il seguente codice:

(clicca sull’immagine per leggere il codice)

arriviamo a questa:

Peccato che non funziona! O meglio, funzionava con la beta 2 ma non con la RTM.

dcVediamo il perchè facendolo con un codice fresco fresco e basato su un esempio davvero banale.

Creiamo un semplice Data Base con due tabelle (Categorie e Prodotto), crediamo il DataContex e la pagina che mostra i dati.

L’estrazione dei dati la facciamo con l’ormai arcinota query:

1 ProductsDataContext dc = new ProductsDataContext(); 2 GridViewProdotti.DataSource = from x in dc.Products 3 orderby x.Codice 4 select x; 5 GridViewProdotti.DataBind();

ottenendo il seguente risultato:

wnd

Ora, proviamo ad aggiungere alla visualizzazione, il nome completo della categoria con il seguente codice:

1 <Columns> 2 <asp:TemplateField HeaderText="Categoria"> 3 <ItemTemplate> 4 <%# Eval("Category.Categoria")%> 5 </ItemTemplate> 6 </asp:TemplateField> 7 </Columns> 8

e, mandando in esecuzione la pagina, a differenza di quanto potremmo aspettarci, vedremo la nuova colonna vuota Undecided

CaptureIl perchè di tale comportamento è tanto banale quanto difficile da individuare.

Per capire cosa accade, proviamo a loggare la query che viene eseguita sul Data Base dall’engine, a partire da quella che abbiamo scritto in Linq.

A tale scopo, ci torna comoda la propietà Log del DataContext. Essa, di tipo TextWriter, ci mostra esattamente la query che viene inviata al Data Base. Se lavoriamo con una applicazione console, ci basterà scrivere

datacontex.Log = Console.Out;

Per altri tipi di applicazione, ho trovato comodissima questa classe che ci permette di loggare direttamente nella finestra di debug di Visual Studio.

Tornando al nostro problema specifico, loggando la query generata, ci troveremo:

SELECT [t0].[IDProdotto], [t0].[Codice], [t0].[Descrizione], [t0].[Prezzo], [t0].[IDCategoria] FROM [dbo].[Products] AS [t0] ORDER BY [t0].[Codice]

Avendo il minimo indispensabile di conoscenze su SQL, è abbastanza evidente che la tabella delle categorie (dove è memorizzato il nome della categoria), non viene interessata dalla query e che quindi, il codice

<%# Eval("Category.Categoria")%>

non può recuperarne il nome.

Ma allora, perchè il codice mostrato da Scott nel suo esempio funziona(va)???

Facendo un po’ di ricerche (di cui non ho conservato il risultato però Frown, ho scoperto che nella Beta2 (usata da Scott per gli esempi), la proprietà ObjectTrackingEnabled del DataContext è impostata a True di default, mentre nella RTM, per questioni di performance, è impostata a False!

Come si legge dalla documentazione ufficiale:

Instructs the framework to track the original value and object identity for this DataContext

Ne consegue che, come si vede dalla query generata, quando ObjectTrackingEnabled è False, non vengono interrogate le tabelle collegate con la conseguenza che i relativi oggetti saranno vuoti.

Il problema specifico lo si risolve semplicemente impostando a True questa proprietà. La query (o meglio le query) che verranno inviate al Data Base saranno quindi le seguenti:

SELECT [t0].[IDProdotto], [t0].[Codice], [t0].[Descrizione], [t0].[Prezzo], [t0].[IDCategoria] FROM [dbo].[Products] AS [t0] ORDER BY [t0].[Codice] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8 SELECT [t0].[IDCategoria], [t0].[Categoria] FROM [dbo].[Categories] AS [t0] WHERE [t0].[IDCategoria] = @p0 -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8 SELECT [t0].[IDCategoria], [t0].[Categoria] FROM [dbo].[Categories] AS [t0] WHERE [t0].[IDCategoria] = @p0 -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8 SELECT [t0].[IDCategoria], [t0].[Categoria] FROM [dbo].[Categories] AS [t0] WHERE [t0].[IDCategoria] = @p0 -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [3] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8 SELECT [t0].[IDCategoria], [t0].[Categoria] FROM [dbo].[Categories] AS [t0] WHERE [t0].[IDCategoria] = @p0 -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [4] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

Da notare l’ottimizzazzione che viene fatta a livello di interrogazione al Data Base: sebbene abbiamo più di un prodotto in categoria 1, la query relativa alla categoria specifica sarà solo una.

A questo punto, la nostra

<%# Eval("Category.Categoria")%>

ci darà il risultato voluto.

Capture2

Prima di concludere, un paio di note relative a questo esempio:

  1. se state usando direttamente il LinqDataSource, per abilitare l’ObjectTrackingEnabled, vi basterà impostare a True la proprietà EnableUpdate direttamente dalle proprietà del controllo
  2. per questo esempio, al fine di facilitarne la comprensione ed evidenziare il tracking delle query, non è stato usato il LinqDataSource. Usando manualmente il DataContext, la proprietà ObjectTrackingEnabled è True di default, a differenza di quanto accade usando il controllo. Di conseguenza, ai fini dell’esempio, l’ho impostata manualmente a false.