3
votes

Pourquoi ne reçois-je PAS une exception de référence nulle?

J'utilise LINQ to Entities pour obtenir des données d'une base de données. Voici ma requête.

foreach (var item in location)
{
    var p = item.EquipmentClass.EquipmentType.Name;
}

Dans le code ci-dessus, ids est une liste d'entiers que je passe pour filtrer les résultats. Lorsque j'obtiens les résultats, je vois que l'un des enregistrements de retour a un EquipmentClass nul. J'avais effectué une vérification nulle mais je me suis rendu compte que j'avais oublié de faire une vérification nulle sur l'une des propriétés. Maintenant, je m'attendrais à obtenir une exception de référence nulle sur EquipmentType = e.EquipmentType.Name mais je ne le fais pas. À ma grande surprise, cela fonctionne très bien et est défini sur null. Mon EquipmentClass a un type de propriété EquipmentType , qui est une autre classe. EquipmentType a une propriété Name qui est une chaîne.

  1. Pourquoi cela ne lance-t-il pas une exception de référence nulle?

Juste à titre de test, j'ai supprimé la vérification de null de InServiceStatus = e == null? false: e.InServiceStatus et échoue avec une exception d'opération non valide lors de l'exécution d'une boucle foreach à l'aide de la requête.

  1. Cela signifie-t-il que je n'ai besoin de vérifier les valeurs nulles que sur les valeurs non nulles?

var location = from l in dbContext.Locations
join e in dbContext.Equipment on l.ID equals e.LocationID into rs1
from e in rs1.DefaultIfEmpty()
where ids.Contains(l.ID)
select new
{
    EquipmentClass = e,
    LocationID = l.ID,
    LocationName = l.Name,
    EquipmentName = e == null ? null : e.Name,
    Description = e == null ? null : e.Description,
    InServiceStatus = e == null ? false : e.InServiceStatus,
    EquipmentType = e.EquipmentType.Name
};

foreach (var item in location)
{
    // some logic
}

Ajouté ceci juste après la requête. Sur l'affectation de p, j'obtiens une exception de référence nulle. Je ne suis pas sûr de savoir comment cela va même loin, car il devrait échouer sur la première ligne de la boucle foreach. Sans la ligne déclarant la variable p, je n'obtiens pas d'exception de référence nulle . Si quelqu'un peut expliquer ce qui se passe, je lui en serais reconnaissant. Pour information, les valeurs de item.EquipmentClass et item.EquipmentType sont toutes deux nulles au moment où la boucle foreach démarre.

Update2 : J'ai trouvé ce lien où il semble que quelqu'un a un problème presque identique en utilisant LINQ to SQL. Je comprends l'essentiel de la réponse mais je ne comprends pas entièrement son impact potentiel sur mes deux questions ci-dessus.


8 commentaires

Je doute que ce soit possible. Sans vérification de null, le code EquipmentType = e.EquipmentType.Name échouera également. Êtes-vous sûr que votre observation est correcte? J'ai essayé un exemple de code rapide à ma fin et il échoue comme prévu.


Article connexe - Linq join iquery, comment utiliser defaultifempty


@RBT En effet, exécuter mon code ne produit aucune erreur, il exécute joyeusement EquipmentType = e.EquipmentType.Name et définit simplement toutes les valeurs sur null (c'est-à-dire e = null, e. EquipmentType = null et e.EquipmentType.Name = null).


Pouvez-vous mettre à jour votre message avec la définition de EquipmentClass ?


J'ai mis à jour ma réponse. Cela échouera si vous faites l'énumération foreach jusqu'à la fin. Il peut passer pour quelques itérations initiales lorsque les correspondances correspondantes sont disponibles pour la jointure.


Je pense que c'est une question similaire: stackoverflow.com/a/13148126


@kapsi très similaire, merci!


Articles connexes - DefaultIfEmpty - LINQ to SQL vs In Memory & Objet nul ou vide lorsque la requête LINQ to Entities ne renvoie rien


3 Réponses :


1
votes

Votre mise à jour m'a aidé à comprendre votre réelle préoccupation. Le concept que vous devez connaître en ce qui concerne les requêtes LINQ est exécution différée dans LINQ .

Veuillez parcourir les liens ci-dessous pour plus de détails:

Quels sont les avantages d'une exécution différée dans LINQ?

Linq - Quel est le moyen le plus rapide de découvrir une exécution différée ou non?

Que se passe-t-il maintenant dans votre cas? Vous avez stocké votre requête dans la variable location . Cette étape particulière n'est que la partie d'initialisation. Il n'exécute pas vraiment la requête sur votre base de données via votre couche ORM. Voici comment vous pouvez tester cela.

Mettez un point d'arrêt sur la ligne de code où vous initialisez la variable location avec la requête LINQ. Lorsque le débogueur s'arrête dans Visual Studio, accédez à SQL Server Management Studio (SSMS) et démarrez une session SQL Server Profiler.

Appuyez maintenant sur F10 dans Visual Studio pour parcourir le code déclaration. À ce stade, vous ne verrez absolument aucune exécution de requête dans la session du profileur comme indiqué ci-dessous:

entrez la description de l'image ici

C'est tout parce que La requête LINQ n'a pas été exécutée du tout jusqu'à présent.

Vous accédez maintenant à votre ligne de code ci-dessous:

class Program
{
    static void Main(string[] args)
    {
        var mylist1 = new List<MyClass1>();
        mylist1.Add(new MyClass1 { id = 1, Name1 = "1" });
        mylist1.Add(new MyClass1 { id = 2, Name1 = "2" });

        var mylist2 = new List<MyClass2>();
        mylist2.Add(new MyClass2 { id = 1, Name2 = "1" });

        var location = from l in mylist1
                       join e in mylist2 on l.id equals e.id into rs1
                       from e in rs1.DefaultIfEmpty()
                       //where ids.Contains(l.ID)
                       select new
                       {
                           EquipmentClass = e,
                           InServiceStatus = e == null ? 1 : e.id,
                           EquipmentType = e.id
                       };

        foreach (var item in location)
        {

        }
    }
}

class MyClass1
{
    public int id { get; set; }
    public string Name1 { get; set; }
}

class MyClass2
{
    public int id { get; set; }

    public string Name2 { get; set; }
}

Le moment où vous entrez dans la boucle foreach, la requête LINQ est déclenchée et vous verrez le journal correspondant dans la session de trace dans le profileur SQL Server. Ainsi, une requête LINQ n'est déclenchée que si elle est énumérée. C'est ce qu'on appelle une exécution différée, c'est-à-dire que le runtime diffère l'exécution jusqu'à l'énumération. Si vous n'énumérez jamais la variable location dans votre code, l'exécution de la requête ne se produira jamais du tout.

Donc, pour répondre à votre requête, une exception ne viendra que lorsque la requête sera déclenchée. Pas avant ça!

Mise à jour 1 : Vous dites que - Je n'obtiens pas d'exception de référence nulle . Oui! vous n'obtiendrez pas d'exception de référence nulle tant que vous n'aurez pas atteint l'enregistrement dont l'enregistrement joint RHS correspondant est manquant. Jetez un œil au code ci-dessous pour une meilleure compréhension:

foreach (var item in location)
{
    var p = item.EquipmentClass.EquipmentType.Name;
}

Donc, maintenant, lorsque je commence à itérer la variable location , elle ne se brise pas en premier itération. Il rompt dans la deuxième itération. Il se casse lorsqu'il ne parvient pas à obtenir l'enregistrement / l'objet correspondant à l'objet MyClass1 ayant id 2 présent dans mylist1 . mylist2 n'a aucun objet avec id 2. J'espère que cela vous aidera!


3 commentaires

Oui, je comprends que la requête ne s'exécute pas tant qu'elle n'est pas énumérée, ce n'est pas ma préoccupation. Je n'obtiens pas d'erreur de référence nulle, même lorsque la boucle est énumérée . J'ai spécifiquement mis le code ci-dessus pour montrer que j'obtiens l'erreur sur l'attribution de p, pas l'énumération de la requête . Si je devais supprimer la ligne avec la variable p dans mon exemple, je n'aurais pas du tout d'exception.


Cela me semble impossible, mais je vais essayer de résoudre votre problème. Veuillez partager la définition de la classe EquipmentClass .


Mon code ne se rompt dans aucune itération, je traverse toute la boucle foreach sans problème. Je sais ce que vous dites et je le comprends. Je dis que quelque chose de complètement différent se passe dans mon cas. Peut-être est-ce spécifique à LINQ to SQL / Entities. J'ai mis à jour mon message avec un lien vers un problème similaire. Dans la réponse sur ce lien, il semble indiquer que LINQ manipule la façon dont les données sont affectées sous le capot, apparemment d'une manière qui ne se traduit pas par une exception de référence nulle à laquelle vous vous attendriez lors de l'utilisation d'objets.



5
votes

Si vous écrivez une requête LINQ sur IQueryable , ce qui se passe, c'est que les méthodes que vous appelez dans les coulisses (telles que Select , Where , etc.) ne faites rien de plus que simplement enregistrer comment vous les avez appelés, c'est-à-dire qu'ils enregistrent les expressions de prédicat et transportent un fournisseur LINQ. Dès que vous commencez à itérer la requête, le fournisseur est invité à exécuter le modèle de requête . Donc, fondamentalement, le fournisseur utilise le modèle d'expression pour vous donner un résultat du type attendu.

Le fournisseur n'est en aucun cas obligé de compiler ou même d'exécuter le code (modèle) que vous avez livré en tant qu'expression. En fait, tout l'intérêt de LINQ to SQL ou LINQ to Entities est que le fournisseur ne fait pas cela et traduit l'expression de code en SQL à la place.

Par conséquent, votre requête est en fait rendue sous forme de requête SQL et le résultat de cette requête est retransformé. Par conséquent, la variable e que vous voyez dans la requête n'est pas nécessairement vraiment créée mais uniquement utilisée par le fournisseur LINQ pour compiler une requête SQL. Cependant, la plupart des serveurs de base de données ont des propagations nulles.

Exécutez la même requête sur LINQ to Objects et vous obtiendrez votre NullReferenceException manquante .


2 commentaires

Serait-il toujours considéré comme une bonne pratique d'effectuer une vérification des valeurs nulles sur les valeurs pouvant être nulles ou serait-ce considéré comme redondant dans ce cas?


@ Sudsy1002 En fait, de nombreuses bases de données permettent de configurer si elles propagent des valeurs nulles, donc dans ce qui précède, votre code dépendrait de cette configuration de base de données. Par conséquent, oui, les vérifications nulles sont une bonne chose, au cas où vous changeriez le fournisseur LINQ ou la configuration de la base de données.



-1
votes

votre e.EquipmentType.Name est null et il est attribué à EquipmentType et il est parfaitement normal d'attribuer null à un type nullable et que vous utilisez DefaultIfEmpty () qui initialisera les éléments avec leur valeur par défaut s'il ne répond à aucune condition et dans ce cas votre e.ElementType.Name est défini sur null , ce qui est bien je suppose. utilisez ToList () qui lèvera l'exception.

J'espère que j'ai du sens et que vous en avez peut-être discuté.


1 commentaires

DefaultIfEmpty n'est pas ce qui permet à e.EquipmentType.Name d'être une valeur nulle. Si j'utilisais LINQ to Objects, la même instruction produirait une exception de référence nulle. Mon objectif n'était pas non plus d'obtenir l'exception, je voulais savoir pourquoi je ne recevais pas l'exception.