J'ai une entité de catégorie qui a un champ Nullable ParentId. Lorsque la méthode ci-dessous est en cours d'exécution et que categoryId est null, le résultat semble nul, mais certaines catégories ont une valeur ParentId nulle.
Quel est le problème ici, qu'est-ce qui me manque?
public IEnumerable<ICategory> GetSubCategories(long? categoryId)
{
var subCategories = this.Repository.Categories.Where(c => c.ParentId == categoryId)
.ToList().Cast<ICategory>();
return subCategories;
}
À propos, quand je change la condition en (c.ParentId == null), le résultat semble normal.
La première chose à faire est de mettre en journalisation, pour voir ce que TSQL a été généré; par exemple:
ctx.Log = Console.Out;
LINQ-to-SQL semble traiter les valeurs NULL de manière un peu incohérente (en fonction de la valeur littérale ou valeur):
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();
}
Donc tout ce que je peux suggérer est d'utiliser la forme supérieure avec des valeurs nulles!
c'est à dire.
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>();
Mise à jour - Je l’ai fonctionné "correctement" avec une Expression
personnalisée:
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);
}
Autre moyen:
Where object.Equals(c.ParentId, categoryId)
ou
Where (categoryId == null ? c.ParentId == null : c.ParentId == categoryId)
Vous devez utiliser l'opérateur Equals:
var subCategories = this.Repository.Categories.Where(c => c.ParentId.Equals(categoryId))
.ToList().Cast<ICategory>();
Equals pour les types nullables renvoie true si:
et retourne false si:
Plus d'infos ici Nullable <.T> .Equals Method
Mon hypothèse est que cela est dû à un attribut assez commun des SGBD - Le fait que deux choses soient nulles ne signifie pas qu'elles sont égales.
Pour élaborer un peu, essayez d’exécuter ces deux requêtes:
SELECT * FROM TABLE WHERE field = NULL
SELECT * FROM TABLE WHERE field IS NULL
La construction "IS NULL" s'explique par le fait que, dans le monde des SGBD, NULL! = NULL puisque NULL signifie que la valeur est indéfinie. Puisque NULL signifie non défini, vous ne pouvez pas dire que deux valeurs nulles sont égales, car par définition, vous ne savez pas ce qu'elles sont.
Lorsque vous vérifiez explicitement "field == NULL", LINQ le convertit probablement en "field IS NULL". Mais lorsque vous utilisez une variable, je suppose que LINQ ne fait pas automatiquement cette conversion.
Voici un message sur le forum MSDN avec plus d’informations sur ce problème.
On dirait qu'un bon "tricheur" consiste à changer votre lambda pour ressembler à ceci:
c => c.ParentId.Equals(categoryId)
Ou vous pouvez simplement utiliser ceci. Cela se traduira également par une requête SQL plus agréable
Where((!categoryId.hasValue && !c.ParentId.HasValue) || c.ParentId == categoryId)
Qu'en est-il quelque chose de plus simple comme ça?
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;
}
Linq to Entities supporte Null Coelescing (??), il suffit donc de convertir le null à la volée en une valeur par défaut.
Where(c => c.ParentId == categoryId ?? 0)