Confessions of a Dangerous Mind

Brain.FlushBuffer()
posts - 176, comments - 234, trackbacks - 93

[LINQ Descent 1] Nullable Integer e Select in C# e VB.net

Ieri, effettuando una query con LINQ su una fonte dati SQL mi sono imbattuto in un comportamento che mi ha spinto a scavare un pò in profondità per quanto riguarda la sintassi di conversione che LINQ adotta quando si vogliono estrarre righe con valori NULL presenti in una tabella.

Cominciamo dall'inizio: tabella semplicissima, Person, con tre campi:

  • Id: int not null PK Identity
  • Name: not null varchar(50)
  • Age: int null

Tutto qui. Dopo aver popolato la tabella come segue vogliamo effettuare delle estrazioni dei dati.

Id Name Age
1 Mario NULL
2 Franco 30
3 Alice 19
4 Luca NULL
5 Alfredo NULL
6 Giuseppe 45

1) C# Estrazione di tutte le righe con Age NULL

var _persons = from _p in _dc.Persons where _p.Age == null select _p;

Debug:

SELECT [t0].[Id], [t0].[Name], [t0].[Age]
FROM [dbo].[Person] AS [t0]
WHERE [t0].[Age] IS NULL
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

Mario Age:
Luca Age:
Alfredo Age:

La query è corretta e la sintassi SQL generata anche. I risultati sono quelli che ci si aspetta.

2) VB Estrazione di tutte le righe con Age NULL

Dim _persons = From _p In _dc.Persons Where _p.Age = Nothing Select _p

Debug:

SELECT [t0].[Id], [t0].[Name], [t0].[Age]
FROM [dbo].[Person] AS [t0]
WHERE (COALESCE(
    (CASE
        WHEN [t0].[Age] = NULL THEN 1
        WHEN NOT ([t0].[Age] = NULL) THEN 0
        ELSE NULL
     END),@p0)) = 1
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [0]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

La query viene tradotta in tutt'altro modo rispetto a C#, e non vi sono risultati. Questo perchè? Se proviamo a modificarla come segue:

Dim _persons = From _p In _dc.Persons Where _p.Age Is Nothing Select _p

Debug:

SELECT [t0].[Id], [t0].[Name], [t0].[Age]
FROM [dbo].[Person] AS [t0]
WHERE [t0].[Age] IS NULL
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

Mario Age:
Luca Age:
Alfredo Age:

Quindi bisogna fare attenzione alla sintassi, se si lavora in VB e ricordare che se si vogliono effettuare confronti con valori NULL nel DB si deve usare Is Nothing e non =Nothing.

Questa cosa, però, ha suscitato in me un'altro interrogativo: e se, al posto di una costante (null o nothing) ci fosse stato un valore nullable integer? Come si sarebbe comportato il sistema se il valore dell'integer fosse stato effettivamente null o nothing?

3) C# variabile nullInt dichiarata ed usata nella query con valore null:

int? nullInt = null;           
var _persons = from _p in _dc.Persons where _p.Age == nullInt select _p;

Debug:

SELECT [t0].[Id], [t0].[Name], [t0].[Age]
FROM [dbo].[Person] AS [t0]
WHERE [t0].[Age] = @p0
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [Null]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

Query "errata" e nessun risultato. Non è ciò che mi sarei aspettato. Se imposto il valore a 45, ottengo un risultato (Giuseppe).

4) VB varibile nullInt dichiarata ed usata nella query con valore Nothing:

Dim _nullInt? As Integer
Dim _persons = From _p In _dc.Persons Where _p.Age = _nullInt Select _p

Debug:

SELECT [t0].[Id], [t0].[Name], [t0].[Age]
FROM [dbo].[Person] AS [t0]
WHERE (COALESCE(
    (CASE
        WHEN [t0].[Age] = @p0 THEN 1
        WHEN NOT ([t0].[Age] = @p0) THEN 0
        ELSE NULL
     END),@p1)) = 1
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [Null]
-- @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [0]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

Ancora una volta, query diversa da C# e nessun risultato.

Ma allora? Cosa significa? Significa che in VB.net, per ottenere la stessa sintassi di C# con i valori null si deve usare o il confronto tramite Is Nothing o con il metodo Equals(Nothing), ovvero:

Dim _persons = From _p In _dc.Persons Where _p.Age.Equals(Nothing) Select _p

Dim _persons = From _p In _dc.Persons Where _p.Age Is Nothing Select _p

var _persons = from _p in _dc.Persons where _p.Age == null select _p;

Sono equivalenti, in termini di SQL generato, e quindi di risultati. La cosa che non si riesce a fare, è quella di utilizzare un nullable type e a seconda del suo valore (se null o valorizzato) ottenere una query che vari, adoperando l'operatore IS nel caso in cui il valore della variabile sia null, oppure = nel caso il valore della variabile sia diverso da null. Questo vale sia per C# che per VB.

Un'altra tecnica per estrarre i valori null, in caso di nullable type, può basarsi sull'utilizzo del metodo HasValue() che viene tradotto con un efficace NOT IS NULL. Scrivendo quindi:

Dim _persons = From _p In _dc.Persons Where Not _p.Age.HasValue Select _p

Si ottengono risultati analoghi a quelli ottenuti tramite confronto con Nothing.

Print | posted on giovedì 21 febbraio 2008 12:20 |

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET