web-dev-qa-db-fra.com

Comparez les types nullables dans Linq à SQL

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.

40
Ali Ersöz

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);
    }
28
Marc Gravell

Autre moyen: 

Where object.Equals(c.ParentId, categoryId)

ou

Where (categoryId == null ? c.ParentId == null : c.ParentId == categoryId)
53
ariel

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:

  • La propriété HasValue a la valeur false et l'autre paramètre, la valeur null. C'est-à-dire que deux valeurs nulles sont égales par définition.
  • La propriété HasValue a la valeur true et la valeur renvoyée par la propriété Value est égale à l'autre paramètre.

et retourne false si:

  • La propriété HasValue pour la structure Nullable actuelle est true et l'autre paramètre est null.
  • La propriété HasValue pour la structure Nullable actuelle est false et l'autre paramètre n'est pas null.
  • La propriété HasValue pour la structure Nullable actuelle est true et la valeur renvoyée par la propriété Value n'est pas égale à l'autre paramètre.

Plus d'infos ici Nullable <.T> .Equals Method

6
algreat

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)
5
Eric Petroelje

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)
1
Jiří Herník

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;
}
1
Ryan Versaw

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)
0
Kevbo