LINQ TO SQL: Uso de NULL

Leyendo C# 3.0 in a Nutshell (Pedazo de libro) he podido aprender acerca del uso de null en consultas LINQ TO SQL y voy a intentar explicarlo como yo lo he entendido.

Partiendo de esta tabla de ejemplo:

linqtosql2

linqtosql

 

Vamos a empezar a lanzar consultas utilizando LINQPad y a observar los resultados:

Customers.Where(c => c.Telephone == null).Dump("IS NULL");

 

linqtosql3

La sentencia SQL que se genera:

SELECT [t0].[CustomerId], [t0].[Name], [t0].[Surname], [t0].[Telephone], [t0].[Email]
FROM [Customers] AS [t0]
WHERE [t0].[Telephone] IS NULL

Como vemos, la instrucción c.Telephone == null, LINQ to SQL la está traduciendo a IS NULL, que como sabemos es la manera de consultar por valores nulos en TSQL.

¿Que pasa sí en vez de utilizar el valor estático null usamos una variable de tipo Nullable<T>?

int? telephone = null;
Customers.Where(c => c.Telephone == telephone).Dump(" = @P");

 

Pues que no retorna nada y el porque se puede apreciar en la consulta SQL que LINQ to SQL emite:

SELECT [t0].[CustomerId], [t0].[Name], [t0].[Surname], [t0].[Telephone], [t0].[Email]
FROM [Customers] AS [t0]
WHERE [t0].[Telephone] = @p0
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [Null]

 

Está claro no? No podemos hacer comparaciones en TSQL del Tipo Campo = NULL

¿Cómo se puede solucionar esto?

Usando object.Equals, que sí el valor de la variable es NULL, LINQ generará un IS NULL y sí tiene valor generará = @p

Customers.Where(c => object.Equals(c.Telephone, telephone)).Dump("object.Equals nullable");

 

linqtosql4

SELECT [t0].[CustomerId], [t0].[Name], [t0].[Surname], [t0].[Telephone], [t0].[Email]
FROM [Customers] AS [t0]
WHERE [t0].[Telephone] IS NULL

 

Y sí la variable tiene valor:

telephone = 999999999;
Customers.Where(c => object.Equals(c.Telephone, telephone)).Dump("object.Equals non nullable");

 

linqtosql5

SELECT [t0].[CustomerId], [t0].[Name], [t0].[Surname], [t0].[Telephone], [t0].[Email]
FROM [Customers] AS [t0]
WHERE ([t0].[Telephone] IS NOT NULL) AND ([t0].[Telephone] = @p0)
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [999999999]

Stored Procedures:

Una cosa que hago desde hace tiempo en mi SPs es utilizar sentencias del tipo:

(ISNULL(Name,0) LIKE ISNULL(@Name,ISNULL(Name,0)))

 

para crear consultas dinámicas y he estado intentando hacerlo desde LINQ to SQL pero no lo he conseguido y no sé sí se podrá claro 🙂

Así que he optado por crear el SPs tal cual el ejemplo:

CREATE PROCEDURE [dbo].[sp_GetCustomers]
    @Name nvarchar(50),
    @Surname nvarchar(50),
    @Telephone int,
    @Email nvarchar(100)
AS
BEGIN
    SET NOCOUNT ON;
 
    SELECT    Name,
            Surname,
            Telephone,
            Email
    FROM    Customers
    WHERE        (ISNULL(Name,0) LIKE ISNULL(@Name,ISNULL(Name,0)))
    AND            (ISNULL(Surname,0) LIKE ISNULL(@Surname,ISNULL(Surname,0)))
    AND            (ISNULL(Telephone,0) = ISNULL(@Telephone,ISNULL(Telephone,0)))
    AND            (ISNULL(Email,0) = ISNULL(@Email,ISNULL(Email,0)))
END
 
Y llamarlo directamente:
 
var customers1 = sp_GetCustomers(null,null,null,null);
 
customers1.Dump("SP ALL NULL");
 
var customers2 = sp_GetCustomers("michael",null,null,null);
 
customers2.Dump("SP Name");

linqtosql6

Pues esto es todo!!!

LINQ: Deferred Execution (Ejecución diferida): Cosas a tener en cuenta

Pues como dice el título, la ejecución diferida es una característica de algunos operadores de LINQ, qué sólo cuando se itera sobre los elementos del enumerable, se evalua la consulta (cuando se llama al método MoveNext) y no cuando se construye.

No todos los operadores de LINQ causan esta ejecución diferida, hay excepciones:

  1. Operadores que retornan un sólo elemento o un valor escalar, como First, Last, Count…
  2. Operadores de conversión como ToArray, ToList…

Vamos a ver todo esto con un ejemplo:

///Deferred Execution
 
List<string> names = new List<string>();
names.Add("Juan");
names.Add("Pepe");
names.Add("Carlos");
names.Add("Jacinto");
 
var greaterthanfour = from n in names
                      where n.Length > 4
                      select n;
 
names[2] = "Ciro";
 
foreach (string name in greaterthanfour)
    Console.WriteLine(name);
 
Console.Read();

 Sí observamos la consulta, en ella se obtienen los nombres cuya longitud es mayor de 4, en tal caso la respuesta debería ser:

Carlos y Jacinto, no?

Pues no, porque como hemos comentado, la consulta no se evalua hasta que se itera sobre ella (instrucción foreach), y por tanto Carlos ha sido sustituido por Ciro, con lo cual el resultado es:

linq1

Otra problema que nos podemos encontrar con la ejecución diferida es la Reevalución, es decir, la reevalución se produce cuando reenumeramos. Dada la siguiente consulta:

///Deferred Execution -> Reevaluation
 
List<string> names = new List<string>();
names.Add("Juan");
names.Add("Pepe");
names.Add("Carlos");
names.Add("Jacinto");
 
var greaterthanfour = from n in names
                      where n.Length > 4
                      select n;
 
Console.WriteLine("foreach 1");
 
foreach (string name in greaterthanfour)
    Console.WriteLine(name);
 
names.Clear();
 
Console.WriteLine("foreach 2");
 
foreach (string name in greaterthanfour)
    Console.WriteLine(name);
 
Console.Read();

En el primer foreach, obtendremos como resultado:

linq2

 Y en el segundo, no se muestra nada :S, porque hemos limpiado la lista y estamos volviendo a reenumerar, lo que conlleva una reevaluación:

linq3

Para evitar la reevaluación de expresiones (Dado que esta reevalución implica un costo adicional, sobre todo sí estamos ejecutando consultas sobre una base de datos), podemos utilizar métodos como ToArray, ToList… que causan la ejecución inmediata de la consulta y posteriormente trabajar sobre esa copia o caché:

///Deferred Execution -> Reevaluation -> Cache Names
 
List<string> namesCache = (from n in names
                           where n.Length > 4
                           select n).ToList();
 
Console.WriteLine("foreach 1");
 
foreach (string name in namesCache)
    Console.WriteLine(name);
 
names.Clear();
 
Console.WriteLine("foreach 2");
 
foreach (string name in namesCache)
    Console.WriteLine(name);
 
Console.Read();

Resultado:

linq4 

Y por último, otro problema que nos encontramos con la ejecución diferida, es cuando dentro una consulta LINQ usamos variables externas o Outer Variables. Veáse el siguiente ejemplo:

///Deferred Execution -> Outer Variables
 
List<int> salary = new List<int>();
salary.Add(1800);
salary.Add(1500);
 
int plus = 1000;
 
IEnumerable<int> totalSalary = salary.Select(s => s + plus);
 
plus = 500;
 
foreach (int total in totalSalary)
    Console.WriteLine(total);
 
Console.Read();

El problema en este caso, es que estamos usando una variable externa (plus) en la consulta, que inicilamente se incializa a 1000, con lo que podríamos esperar que el resultado fuera o fuese 2800 y 2500, pero antes de iterar, cambiamos su valor a 500, y es este último el que se utilizará para calcular el salario total:

linq5

Así que a partir de ahora, cuidado con la ejecución diferida 😉

Bibliografía: C# 3.0 in a Nutshell

Esto es todo!!!