Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

Entity Framework : Load et AsNoTracking

entity_image

[new:30/04/2015]Entity Framework est d’une grande richesse et depuis un moment déjà elle est une technologie mature et efficace. Ce qui ne l’empêche pas d’évoluer, mais souvent quand on connait bien une techno on loupe les nouveautés qui deviennent des vieilleries entre temps... C’est le cas de deux ajouts à EF 4.1 qui valent pourtant le détour…

EF 4.1 une version pas si jeune

Entity framework a été inclus pour la première fois dans le Framework .NET 3.5 Service Pack 1, époque de Visual Studio 2008 donc releasé en 2008, le 11 août pour être précis.

Nous sommes bientôt en août 2015 (j’aime être optimiste !) EF existe donc depuis 7 ans.

Je vous fais grâce de l’historique complet pour en arriver à cet heureux jour du 12 avril 2011 où la version 4.1 a été diffusée.

Depuis on a connu les versions 4.x et la 5.0 sortie en 2012 ainsi que la 6.0 en 2013.

De l’eau a donc coulé sous les ponts depuis la sortie de la 4.1.

Deux méthodes peu utilisées et pourtant…

Parmi les nombreuses évolutions de Entity Framework certaines ont été forcément plus marquantes que d’autres et tous ceux qui utilisent cette technologies ont au minimum “vu passer” l’information. Je pense par exemple aux options tournant autour du code first qui permet de créer la base à partir de déclarations dans le code plutôt que seulement mapper des champs existants dans une base existante.

Dans ce flot d’ajouts je m’aperçois en regardant le code des uns et des autres au cours de mes interventions d’audit ou autre que bizarrement deux méthodes introduites pourtant il y a déjà quelques temps sont peu ou carrément pas utilisées du tout.

Et c’est très dommage car elles ont un rôle essentiel à jouer dans la conception d’application plus rapides, plus fluides et moins consommatrices de mémoire.

Ces deux méthodes sont Load et AsNoTracking.

La facilité a ses défauts..

Tout ce qui est facile, automatique, généré, etc, est forcément un avantage en terme de productivité. Mais toutes ces facilités ont en général des défauts et au moins quelques uns en commun que sont généralement la lenteur et la consommation mémoire, l’un ou l’autre voire les deux.

Entity Framework est une technologie époustouflante. Elle rend la conception d’applications orientées données plus simple, plus fiable, plus rapide. Associé à LINQ c’est même d’une puissance redoutable tout en créant une véritable isolation entre le code et la base de données.

L’un des gros avantages de EF c’est sa capacité à pister les objets. Vous charger une grappe, vous bricolez quelques champs ici et là, supprimez des enregistrements, en ajoutez d’autres, un petit update du contexte et hop! c’est dans la poche EF s’occupe de générer toutes les opérations SQL pour créer de nouvelles lignes dans les tables, en détruire d’autres, faire les mises à jour, créer de nouveaux ID et les propager aux lignes dépendantes… C’est presque magique.

Hélas cette énorme simplification qu’apporte EF se paye. Car pour pister ainsi les enregistrements transformés en objets EF gère des listes en mémoire. Et on retrouve ici les inconvénients typiques évoqués plus haut : consommation des ressources processeur et de la mémoire.

EF est vraiment bien fait et tout cela reste très maitrisé comparé à d’autres systèmes de mapping objet qui ressemblent à des usines à gaz. Même le code SQL produit par EF quand on l’analyse se trouve souvent être plutôt du bon code efficace.

Dans un grand nombre de cas il est parfaitement justifié de payer le prix des facilités proposées par EF car les gains (fiabilité, code SQL propre, lisibilité du code, concision de celui-ci…) valent très largement quelques octets en mémoire en quelques millisecondes de plus passées dans la gestion des listes internes.

Toutefois il existe aussi de très nombreux cas où ce prix ne couvre pas les frais… Charger une liste pour faire une combobox ou une listbox, charger une grappe d’objets pour générer un état, et bien d’autres cas sont des exemples où se “trainer” toute la gestion de tracking de EF finit par peser bien plus lourd que les avantages puisqu’ici les objets ne sont pas destinés à être créés, modifiés ou supprimés.

Jusqu’à la version 4.1 il existait quelques ruses mais rien de bien codifié dans EF lui-même pour gérer ces cas pourtant bien réels. Puis ont été introduites les deux méthodes évoquées depuis le début de cet article. Elles viennent justement répondre chacune à leur façon au besoin de charger les données immédiatement sans consommer trop de ressources.

Et comme je trouve que ces méthodes sont peu utilisées je me suis dis qu’en parler ferait donc du bien à tout le monde. Je m’excuse pour ceux qui en roulant des mécaniques diront qu’ils ne connaissent que ça, je vous promet de vous parler de trucs que vous ignorez prochainement ! Mais pour tous les autres ça sera là une occasion d’écrire un code de meilleur qualité…

Load

Il existe donc plusieurs scénarios où charger des entités de la base de données dans le contexte EF sans faire immédiatement quelque chose avec ces entités est fort utile car rappelons qu’une requête LINQ/EF ne s’exécute pas forcément tout de suite et si on ferme le contexte les données pourront ne pas avoir été chargées. La “ruse” habituelle est d'écrire une requête LINQ et d’appeler à sa suite ToList() sur ​​son résultat ce qui aura pour effet de charger tout de suite les données. La méthode d'extension Load() fonctionne comme ToList() sauf qu’elle évite de créer la liste pour ensuite l’abandonner ce qui est un peu stupide. Avec Load() aucune liste n’est créée mais les données sont chargées et on peut itérer pour les parcourir.

Voici deux exemples d'utilisation de Load. Le premier est tiré d'un application de gestion de données classique en Windows Form où la méthode est utilisée pour interroger des entités avant de les lier par binding à un objet d’UI pour afficher une liste de catégories, utilisation des données qui ne justifie pas de s’encombrer avec une liste intermédiaire mais qui réclame que toutes les données soient chargées (ces exemples de code sont tirés de MSDN) :

protected override void OnLoad(EventArgs e) 
{ 
    base.OnLoad(e); 
    context = new ProductContext(); 
    context.Categories.Load(); 
    categoryBindingSource.DataSource = context.Categories.Local.ToBindingList(); 
}

 

Le second exemple montre le chargement d’une collection d’entités filtrées :

using (var context = new BloggingContext()) 
{ 
    var blog = context.Blogs.Find(1); 
 
    // Charge les posts avec le tag 'entity-framework'
    context.Entry(blog) 
        .Collection(b => b.Posts) 
        .Query() 
        .Where(p => p.Tags.Contains("entity-framework") 
        .Load(); 
}

 

S’il n’y a rien d’époustouflant dans l’utilisation de Load() c’est à la fois le gain de temps et de mémoire qui font la différence mais aussi la qualité du code qui se trouve améliorée. Pas de ToList() ici, on exploite une requête LINQ/EF directement sans passer par des intermédiaires inutiles.

AsNoTracking

Il s’agit toujours d’obtenir des entités d'une requête mais ici on cherche à éviter toute la gestion du tracking du contexte. La requête reste une requête EF/LINQ qui sera exécutée quand cela sera nécessaire (donc pas tout de suite). Les performances seront meilleures qu’une requête classique lors de l'interrogation d'un grand nombre d'entités dans tous les scénarios en lecture seule. Il est donc important de s’en servir partout où les données sont en lecture seules (combobox, listbox, liste de choix à afficher, impression, etc).

La méthode d'extension AsNoTracking permet à toute requête de s’exécuter de cette manière. Par exemple:

using (var context = new BloggingContext()) 
{ 
    // obtenir tous les blogs sans le tracking 
    var blogs1 = context.Blogs.AsNoTracking(); 
 
    // obtenir quelques blogs sans tracking 
    var blogs2 = context.Blogs 
                        .Where(b => b.Name.Contains(".NET")) 
                        .AsNoTracking() 
                        .ToList(); 
}

 

Au passage on remarque que ToList() peut être utilisé, ce n’est pas lui qui pose problème mais bien de laisser EF créer des listes de tracking qui ne seront pas utilisées. AsNoTracking dans le second exemple évite le tracking et le ToList() sert uniquement de déclencheur du chargement des données.

Nuance

Quelle nuance entre Load() et AsNoTracking() ?

Les documentations ne sont pas très claires sur la question et le web reste bien muet sur le sujet (si vous voulez des images de chats en revanche je peux vous en trouver plein).

Toutefois la nuance existe…

Avec Load() on force l’exécution de la requête sans la différer, les données sont là. C’est comme un ToList() mais sans créer la liste ce qui gagne du temps.

Avec AsNoTracking les données ne sont pas chargées tout de suite (elles le sont quand c’est nécessaire ce qui implique d’avoir un contexte ouvert) mais elles ne possèdent pas d’information de tracking ce qui fait gagner de la mémoire (et du temps).

On peut même dire que Load() et AsNoTracking sont opposées… La première exécute la requête et piste les données dans le contexte sans les retourner alors que AsNoTracking exécute la requête et retourne le résultat mais sans le pister…

Conclusion

Peut-être que le peu d’explications sur la nuance entre ces deux méthodes explique que par méfiance ou incompréhension elles ne soient pas utiliser partout où cela aurait un sens. Il est vrai que ce n’est pas clair dans les documentations.

Il y a une sorte d’ambivalence, d’un côté ces méthodes semblent faire des choses proches, de l’autre dans la réalité on peut les décrire comme étant opposées… C’est forcément troublant.

J’espère que j’aurai levé un peu le voile et que si je passe un jour pour auditer votre code j’aurai le plaisir de voir ces deux méthodes partout où elles ont leur place Sourire

Stay Tuned !

Faites des heureux, partagez l'article !
blog comments powered by Disqus