LINQ perd la mémoire

Il existe un cas d’usage où le comportement de LINQ to Entities n’est pas naturel et peut entraîner des erreurs peu faciles à détecter. La seule chose qu’on observe est que certaines conditions ne sont pas prises en compte, seule la dernière étant prise en considération.

Je vais notamment détailler le cas où l’on construit une requête LINQ (donc un SELECT en base de données) dynamiquement et surtout itérativement, c’est à dire où la requête LINQ doit être construite en utilisant une boucle. Ce genre de requête peut être très fréquente lorsqu’on réalise une recherche, ou un moteur de recherche assez souple ou à spectre large par exemple.

Voici en exemple le comportement bizazrre relatif à LINQ ; prenons le code suivant:

var myList = new int[] { 1, 2, 3, 4, 5, 6, 7 };
var query = myList.Select(l => l);
foreach (var el in new int[] { 1, 2, 3 })
{
	query = query.Where(q => q <= el);
}

En principe, on s’attend à ce que ce code soit identique à :

var myList = new int[] { 1, 2, 3, 4, 5, 6, 7 };
var query = myList.Select(l => l);
query = query.Where(q => q <= 1 && q <= 2 && q <= 3);

Or si l’on exécute les extraits de code, on observe que dans le premier extrait query renvoie un ensemble contenant 1, 2 et 3, alors qu’avec le second extrait, query renvoie un ensemble ne contenant que 1.
En réalité, le premier extrait de code est équivalent à ceci :

var myList = new int[] { 1, 2, 3, 4, 5, 6, 7 };
var query = myList.Select(l => l);
var query1 = query.Where(q => q <= 3 && q <= 3 && q <= 3);
// ce qui revient à
var query2 = query.Where(q => q <= 3);

Mais que s’est-t-il passé ?! On observe que lors de la construction d’une requête LINQ to Entities dans une boucle, la variable de boucle est considérée comme identique. C’est-à-dire que dans mon exemple, LINQ considère que el  est le même à chaque itération, ou plus exactement LINQ ajoute la condition telle quelle textuellement, et en associant à el  une valeur qui est la dernière valeur connue de el .
A la fin de la dernière itération, la requête LINQ contient donc 3 fois la même condition, (q <= el ), et y a associé pour el , sa dernière valeur, soit 3.

Ce comportement est logique lorsqu’on sait comment LINQ fonctionne en interne, mais paraît particulièrement anti-naturel la première fois qu’on rencontre ce comportement.

L’astuce pour contourner ce défaut (présent au moins dans la version 4 du .NET Framework), consiste à stocker el  dans une variable à portée locale, donc à définir une variable alpha  dans la boucle foreach  et à y affecter el . Dans ce cas, il n’y aura aucune ambiguïté sur la variable et chaque alpha  sera différent dans la requête.

Explication

Lorsqu’on construit la requête à coup d’appel successif à Where , la requête n’est pas exécutée et rien n’est évaluée, elle est construite tout comme on ferait une concaténation de chaîne.
A chaque itération, un appel à Where  génère l’expression q => q <= el (même si fonctionnellement on confond parfois les notions, cette lambda expression n’est pas un délégué ou une méthode, c’est une expression, un objet qui peut être parsé, au même titre qu’une formule mathématique peut être lu pour être interprété), où q  est une variable complètement définit puisque paramètre de l’expression, en revanche la question se pose pour el . Il faut considérer la variable comme une référence (au sens C++) vers la valeur, étant donné que el  est la variable de boucle, à chaque itération sa valeur change mais sa référence reste la même (c’est à dire que la la boite contenant la valeur ne change pas, mais la valeur dans la boite change à chaque tour de boucle).

A l’inverse, lorsqu’on déclare une variable alpha  à l’intérieur de la boucle, on s’assure que la référence de alpha  entre deux itérations est différente (puisqu’on la redéclare à chaque fois).

D’une itération à une autre, on peut alors se demander pourquoi lorsqu’on redéclare alpha , le runtime n’utilise jamais une référence précédemment affecté ? En effet on peut se dire que lorsqu’on passe à l’itération suivante, le alpha  d’avant n’est plus utilisé. C’est faux, les alpha  des itérations précédentes sont utilisés : la requête query  détient une référence vers chaque valeur prises par alpha . Du point de vue du runtime, les blocs mémoires sont donc non libre et le runtime affecte donc un autre bloc mémoire pour alpha  à chaque nouvelle itération.

Voir aussi

Réparer un ventilateur bruyant

Voici une petite astuce simple et peu chère pour donner une seconde vie aux ventilateurs asthmatiques. Il se peut même qu’il soit encore plus silencieux qu’à son tout premier ronronnement.

L’astuce n’est pas de moi, mais ça marche diablement bien ! Avant de devoir claquer 40€ dans un nouveau couple ventirad (et me retrouver avec un autre radiateur dans le placard), autant chercher au préalable si on peut le réparer, surtout si la taille du ventilateur est du genre exotique (12mm en hauteur, 100mm en largeur et longueur), donc le genre qu’on ne peut trouver en boutique, même chez le chinois.

Voici deux sources qui expliquent la manipulation :

Matériel requis

  • un ou plusieurs tournevis cruciformes pour ouvrir le PC et démonter le ventilateur
  • un tournevis plat
  • une bombe à huile ou lubrifiante, de préférence avec un prolongateur souple pour ne pas asperger tout le ventilateur, mais ce n’est pas obligatoire
  • 20 minutes maximum

ManipulationS

  1. Éteindre le PC (optionnel, mais recommandé, un court-circuit est si vite arrivé)
  2. Débrancher et démonter le ventilateur. Cela peut prendre plus ou moins de temps en fonction du ventilateur (ventilateur standard, de CPU ou de GPU). Je ne détaillerai pas ce point, ce n’est pas l’objet du billet.
  3. Prendre le ventilateur, en ayant le côté creux des pales, face à soit. On peut voir qu’un autocollant recouvre le centre du ventilateur (si ce n’est pas le cas, c’est que vous vous êtes trompé de côté)
  4. Enlever délicatement l’autocollant car il faudra le remettre, avec l’aide éventuelle d’un tournevis plat.
  5. Si le centre du ventilateur sous l’autocollant est barbouillé de colle, essayer de gratter avec le tournevis plat, ou utiliser de l’acétone en évitant d’arroser les circuits électroniques s’il y en a.
  6. Enlever le capuchon au centre du ventilateur (le capuchon peut être en caoutchouc ou transparent).
  7. Injecter au centre un peu d’huile.
  8. Nettoyer la surface qui est au contact de l’autocollant si elle a été aspergée d’huile (un jet suffit, il n’est pas nécessaire de noyer le moteur), sinon plus rien ne collera dessus.
  9. Remettre le capuchon, puis l’autocollant.
  10. Remonter puis rebrancher le ventilateur.
  11. done 😉

Retour d’expérience

L’astuce a bien fonctionné, j’ai retrouvé mes ventilateurs silencieux, en revanche cela n’a pas duré très longtemps, pas plus de 2 mois :/