Nota
O acceso a esta páxina require autorización. Pode tentar iniciar sesión ou modificar os directorios.
O acceso a esta páxina require autorización. Pode tentar modificar os directorios.
Introducción
Las bases de datos SQL funcionan en lógica con valores 3 (true, false, null) al realizar comparaciones, en lugar de la lógica booleana de C#. Al traducir consultas LINQ a SQL, EF Core intenta compensar la diferencia mediante la introducción de comprobaciones null adicionales para algunos elementos de la consulta.
Para ilustrar esto, vamos a definir la siguiente entidad:
public class NullSemanticsEntity
{
public int Id { get; set; }
public int Int { get; set; }
public int? NullableInt { get; set; }
public string String1 { get; set; }
public string String2 { get; set; }
}
y realizan varias consultas:
var query1 = context.Entities.Where(e => e.Id == e.Int);
var query2 = context.Entities.Where(e => e.Id == e.NullableInt);
var query3 = context.Entities.Where(e => e.Id != e.NullableInt);
var query4 = context.Entities.Where(e => e.String1 == e.String2);
var query5 = context.Entities.Where(e => e.String1 != e.String2);
Las dos primeras consultas generan comparaciones simples. En la primera consulta, ambas columnas no admiten valores NULL, por lo que no se necesitan comprobaciones null. En la segunda consulta, NullableInt podría contener null, pero Id no acepta valores NULL; comparar null con un valor no nulo produce null como resultado, lo cual se filtra mediante la operación WHERE. Por lo tanto, tampoco se necesitan términos adicionales.
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[Id] = [e].[Int]
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[Id] = [e].[NullableInt]
La tercera consulta presenta una comprobación nula. Cuando NullableInt es null, la comparación Id <> NullableInt produce null, que se filtraría en la operación WHERE. Sin embargo, desde la perspectiva de la lógica booleana, este caso debe devolverse como parte del resultado. Por lo tanto, EF Core agrega la comprobación necesaria para asegurarse de ello.
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[Id] <> [e].[NullableInt]) OR [e].[NullableInt] IS NULL
Las consultas cuatro y cinco muestran el patrón cuando ambas columnas admiten valores NULL. Cabe destacar que la <> operación genera una consulta más complicada (y potencialmente más lenta) que la == operación.
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[String1] = [e].[String2]) OR ([e].[String1] IS NULL AND [e].[String2] IS NULL)
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE (([e].[String1] <> [e].[String2]) OR ([e].[String1] IS NULL OR [e].[String2] IS NULL)) AND ([e].[String1] IS NOT NULL OR [e].[String2] IS NOT NULL)
Tratamiento de valores anulables en funciones
Muchas funciones de SQL solo pueden devolver un null resultado si algunos de sus argumentos son null. EF Core aprovecha esto para generar consultas más eficaces.
La consulta siguiente muestra la optimización:
var query = context.Entities.Where(e => e.String1.Substring(0, e.String2.Length) == null);
El CÓDIGO SQL generado es el siguiente (no es necesario evaluar la SUBSTRING función, ya que solo será NULL cuando alguno de los argumentos sea null).
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[String1] IS NULL OR [e].[String2] IS NULL
La optimización también se puede usar para funciones definidas por el usuario. Consulte la página de asignación de funciones definidas por el usuario para obtener más detalles.
Escritura de consultas eficientes
Comparar columnas que no aceptan valores NULL es más sencilla y más rápida que comparar columnas que aceptan valores NULL. Considere la posibilidad de marcar columnas como que no aceptan valores NULL siempre que sea posible.
La comprobación de la igualdad (
==) es más sencilla y más rápida que la comprobación de la no igualdad (!=), ya que la consulta no necesita distinguir entrenullyfalseel resultado. Use la comparación de igualdad siempre que sea posible. Sin embargo, simplemente negar==la comparación equivale eficazmente a!=, por lo que no mejora el rendimiento.En algunos casos, es posible simplificar una comparación compleja filtrando
nulllos valores de una columna explícitamente; por ejemplo, cuando no hay valoresnullpresentes o estos valores no son relevantes en el resultado. Considere el ejemplo siguiente:
var query1 = context.Entities.Where(e => e.String1 != e.String2 || e.String1.Length == e.String2.Length);
var query2 = context.Entities.Where(
e => e.String1 != null && e.String2 != null && (e.String1 != e.String2 || e.String1.Length == e.String2.Length));
Estas consultas generan el siguiente código SQL:
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ((([e].[String1] <> [e].[String2]) OR ([e].[String1] IS NULL OR [e].[String2] IS NULL)) AND ([e].[String1] IS NOT NULL OR [e].[String2] IS NOT NULL)) OR ((CAST(LEN([e].[String1]) AS int) = CAST(LEN([e].[String2]) AS int)) OR ([e].[String1] IS NULL AND [e].[String2] IS NULL))
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[String1] IS NOT NULL AND [e].[String2] IS NOT NULL) AND (([e].[String1] <> [e].[String2]) OR (CAST(LEN([e].[String1]) AS int) = CAST(LEN([e].[String2]) AS int)))
En la segunda consulta, los resultados de null se filtran explícitamente de la columna String1. EF Core puede tratar la columna String1 de forma segura como no anulable durante la comparación, lo que resulta en una consulta más sencilla.
Uso de la semántica de valores NULL relacionales
Es posible deshabilitar la compensación de comparación nula y usar la semántica relacional nula directamente. Para ello, llame al método UseRelationalNulls(true) en el generador de opciones dentro del método OnConfiguring.
new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();
Advertencia
Al usar la semántica de valores NULL relacionales, las consultas LINQ ya no tienen el mismo significado que en C#, y pueden producir resultados diferentes de los esperados. Tenga cuidado al usar este modo.