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.