2009-02-25 32 views
39

Tengo una entidad de categoría que tiene un campo Nullable ParentId. Cuando se ejecuta el método siguiente y la categoría Id es nula, el resultado parece nulo, sin embargo, hay categorías que tienen un valor nulo para ParentId.Comparar tipos anulables en Linq a Sql

¿Cuál es el problema aquí, qué me estoy perdiendo?

public IEnumerable<ICategory> GetSubCategories(long? categoryId) 
{ 
    var subCategories = this.Repository.Categories.Where(c => c.ParentId == categoryId) 
     .ToList().Cast<ICategory>(); 

    return subCategories; 
} 

Por cierto, cuando cambio la condición de que (c.ParentId == null), resultado parece normal.

+1

Encontré una manera ... actualizará ... –

Respuesta

27

Lo primero que debe hacer es iniciar sesión, para ver qué se generó TSQL; por ejemplo:

ctx.Log = Console.Out; 

LINQ a SQL parece tratar a los nulos un poco inconsistente (dependiendo literal vs valor):

using(var ctx = new DataClasses2DataContext()) 
{ 
    ctx.Log = Console.Out; 
    int? mgr = (int?)null; // redundant int? for comparison... 
    // 23 rows: 
    var bosses1 = ctx.Employees.Where(x => x.ReportsTo == (int?)null).ToList(); 
    // 0 rows: 
    var bosses2 = ctx.Employees.Where(x => x.ReportsTo == mgr).ToList(); 
} 

Así que todo lo que puedo sugerir es utilizar el formulario de la parte superior con nulos!

es decir

Expression<Func<Category,bool>> predicate; 
if(categoryId == null) { 
    predicate = c=>c.ParentId == null; 
} else { 
    predicate = c=>c.ParentId == categoryId; 
} 
var subCategories = this.Repository.Categories 
      .Where(predicate).ToList().Cast<ICategory>(); 

Actualización - Yo tengo trabajo "correctamente" por medio de una costumbre Expression:

static void Main() 
    { 
     ShowEmps(29); // 4 rows 
     ShowEmps(null); // 23 rows 
    } 
    static void ShowEmps(int? manager) 
    { 
     using (var ctx = new DataClasses2DataContext()) 
     { 
      ctx.Log = Console.Out; 
      var emps = ctx.Employees.Where(x => x.ReportsTo, manager).ToList(); 
      Console.WriteLine(emps.Count); 
     } 
    } 
    static IQueryable<T> Where<T, TValue>(
     this IQueryable<T> source, 
     Expression<Func<T, TValue?>> selector, 
     TValue? value) where TValue : struct 
    { 
     var param = Expression.Parameter(typeof (T), "x"); 
     var member = Expression.Invoke(selector, param); 
     var body = Expression.Equal(
       member, Expression.Constant(value, typeof (TValue?))); 
     var lambda = Expression.Lambda<Func<T,bool>>(body, param); 
     return source.Where(lambda); 
    } 
+2

Parece que no hay mejor manera de manejar esto. ¡Gracias! –

+2

Me encontré con este mismo problema, hice la misma solución y estaba a punto de preguntar si había una mejor manera de hacerlo. Parece que no hay :( Este comportamiento es realmente contrario a la intuición –

+2

Diría que la inconsistencia entre literales y variables es peor que el contador intuitivo. Gracias por confirmar mi sospecha. +1 – Jodrell

5

Mi conjetura es que es debido a una cualidad bastante común de DBMS - El hecho de que dos cosas sean ambas nulas no significa que sean iguales.

Para elaborar un poco, intente ejecutar estas dos consultas:

SELECT * FROM TABLE WHERE field = NULL 

SELECT * FROM TABLE WHERE field IS NULL 

La razón de la construcción de "IS NULL" es que en el mundo DBMS, NULL = NULL ya que el significado de NULL es que el el valor no está definido Como NULL significa indefinido, no puede decir que dos valores nulos son iguales, ya que, por definición, usted no sabe qué son.

Cuando selecciona explícitamente "campo == NULL", LINQ probablemente lo convierta en "campo IS NULL". Pero cuando utilizas una variable, supongo que LINQ no realiza esa conversión automáticamente.

Aquí está an MSDN forum post con más información sobre este tema.

parece un buen "trampa" es cambiar su lambda para tener este aspecto:

c => c.ParentId.Equals(categoryId) 
+1

Puede alterar el comportamiento de NULL = NULL en MSSQL al establecer el conmutador ansi nulls. Consulte: http://msdn.microsoft.com/en- us/library/aa259229 (SQL.80) .aspx –

+0

¡no! ¡aún nada! :/ –

+4

Es necesario utilizar Object.equals (a, b), que trabajó para mí, donde a.Equals (b) no –

1

¿Qué pasa con algo más simple como esto?

public IEnumerable<ICategory> GetSubCategories(long? categoryId) 
{ 
    var subCategories = this.Repository.Categories.Where(c => (!categoryId.HasValue && c.ParentId == null) || c.ParentId == categoryId) 
     .ToList().Cast<ICategory>(); 

    return subCategories; 
} 
52

Otra forma:

Where object.Equals(c.ParentId, categoryId) 

o

Where (categoryId == null ? c.ParentId == null : c.ParentId == categoryId) 
+0

Esto funcionó muy bien –

+0

Esto funciona perfectamente para mí.Entonces, ¿debemos utilizar de manera predeterminada Equals (x, y) en lugar de "==" en nuestro predicado o hay otros errores con Equals? – AngieM

6

Es necesario utilizar el operador es igual a:

var subCategories = this.Repository.Categories.Where(c => c.ParentId.Equals(categoryId)) 
     .ToList().Cast<ICategory>(); 

Igual fot tipos anulables re Resulta cierto si:

  • La propiedad HasValue es falsa, y el otro parámetro es nulo. Es decir, dos valores nulos son iguales por definición.
  • La propiedad HasValue es verdadera y el valor devuelto por la propiedad Value es igual al otro parámetro.

y devuelve falsa si:

  • La propiedad HasValue para la estructura Anulable corriente es cierto, y el otro parámetro es nulo.
  • La propiedad HasValue para la estructura actual Anulable es falsa, y el otro parámetro no es nulo.
  • El HasValue propiedad para la estructura actual anulable es cierto, y el valor devuelto por la propiedad Value no es igual a los otros parámetros.

Más información aquí Nullable<.T>.Equals Method

+0

Esta es una respuesta correcta – tggm

+1

He probado esto en LinqPad y parece que esto no funciona. Si pasa el literal 'nulo', el sql generado prueba Categories.ParentID IS NULL como era de esperar. Pero si pasa una variable, prueba Categories.ParentID = p0, que no funcionará si p0 es nulo. El enfoque object.Equals (Categories.ParentID, value) de @ariel funcionó muy bien. –

1

o simplemente puede usar esto. También se traducirá en una consulta SQL más agradable

Where((!categoryId.hasValue && !c.ParentId.HasValue) || c.ParentId == categoryId) 
0

LINQ a Entidades apoya nulo Coelescing (??) por lo que sólo convertir el nulo sobre la marcha a un valor predeterminado.

Where(c => c.ParentId == categoryId ?? 0) 
Cuestiones relacionadas