Formation MongoDB M101N: semaine 1 – introduction

Quelques caractéristiques:
– “Rich documents”: on peut stocker davantages que de simples valeurs,
– “Prejoin” en utilisant des documents intégrés (embedded documents),
– Pas de jointures en MongoDB,
– Pas de contraintes,
– Pas de transactions,
– Pas de schéma conceptuel.

Le principe général d’une base MongoDB est d’avoir des schémas de données proches de l’application.

Les bases relationnelles sont conçues pour:
– Interdire de modifier la base en introduisant des anomalies,
– Minimise le redesign de la base quand le schéma conceptuel grossit,
– Il permet d’éviter d’être influencer par certains patterns d’accès.

MongoDB ne pousse pas à utiliser un pattern par rapport à un autre. Ensuite il faut éviter d’introduire des anomalies à la création de données.

Exemple d’articles d’un blog

Par exemple, pour afficher les articles d’un blog, on peut stocker tous les éléments de l’article:
– titre,
– auteur,
– contenu,
– date,
– mots clé.

Tous les commentaires:
– auteur du commentaire,
– email,
– contenu du commentaire.

Tous ces éléments peuvent être stockés dans la même collection en utilisant des documents intégrés (embedded document).

Si on utilise un schéma proche de celui qu’on aurait utilisé dans une base relationnelle, on aurait:
– une collection pour les articles avec un ID, titre, corps, auteur et date,
– une collection pour les commentaires avec un ID, un ID d’article (clé étrangère), auteur, adresse email et l’ordre d’affichage du commentaire,
– une collection pour les tags avec un ID, un tag et un ID d’article (clé étrangère).

Ce type de schéma impose de
– maintenir une relation entre les 3 collections en utilisant des clés étrangères,
– maintenir l’ordre dans la collection des commentaires,
– d’utiliser 3 fichiers sur le disque, 1 fichier par collection.
– d’accèder à 3 collections pour afficher un article.

Cette organisation est plus lourde que de considérer une collection pour stocker tous les éléments.

Contraintes

Un des avantages de la base relationnelle est de garantir d’avoir des données consistantes en utilisant des clés étrangères. MongoDB ne garantit pas d’avoir des clés étrangères consistantes.
Le fait d’utiliser des documents intégrés garantit que les documents sont liés entre eux: si tous les commentaires sont directement dans la collection d’articles, par construction ils appartiennent à l’article.

Transactions

Pas de transactions en MongoDB mais les opérations sont atomiques.
Quand on insère des documents dans une collection, ils ne seront visibles que si le document est ajouté entièrement. En revanche, l’ajout de plusieurs documents n’est pas atomiques.
Le fait d’utiliser des documents intégrés permet de garantir la consistance des données.

En utilisant MongoDB, il faut:
– être restrictif en utilisant des documents intégrés ce qui permet d’insérer des données de façon consistante.
– implémenter des “locks” au niveau software
– tolérer une inconsistance dans les données.

Relations entre les objets logiques

Relations un vers un

Par exemple un employé et un CV, un employé n’a qu’un CV.
On peut stocker les infos dans 2 collections:

Employee:
- _ID,
- Name,
- Resume_ID
Resume 
- _ID,
- Jobs: [] (array)
- Education: []

Ou alors on inclut le CV directement dans la collection "Employee" sous forme de document intégré.

Pas de restrictions pour les relations un vers un, il faut juste avoir en tête:
La fréquence d’accès aux données: si on accède très souvent au CV d’un employé ou si on accède seulement aux informations de l’employé.
La taille des éléments: les informations du CV seront peut être plus volumineuse que celles de l’employé lui-même.
La limite de 16mb d’un document intégré: un document intégré ne peut dépasser 16mb.

Relations un vers plusieurs

Par exemple, une ville et une personne. Une ville contient plusieurs personnes.

Plusieurs schémas sont possibles:
1ere solution:

City:
- name,
- area;
- people: [] (array)

Ce schéma n’est pas possible car il y a beaucoup trop de personnes pour une ville, le tableau serait trop grand.

2e solution:

People:
- name,
- city : { name; area; }

Le problème avec ce schéma est de multiplier les données relatives à la ville dans tous les documents des personnes.

3e solution: 2 collections:

People:
- name;
- city: "NYC"
City:
- _ID: "NYC" 
- area

La 3e solution est la meilleure puisqu’on ne duplique pas les informations.

La 1ère solution aurait pu être choisi si il n’y avait pas beaucoup de personnes.

Pour les relations un vers plusieurs, l’important est de savoir l’ordre de grandeur du plusieurs:
dans le cas d’un grand nombre: il faut considérer une collection différente
dans le cas d’un nombre pas très élevé: il est préférable de privilégier un document intégré.

Plusieurs vers plusieurs

Par exemple, des livres et des auteurs.
Dans ce cas, il y a peu de livres pour un auteur et inversement, il a peu d’auteurs pour un livre. Donc il peut considérer 2 collections:

Books:
- _ID, 
- title, 
- authors: [ author_ID ]
Author: 
- _ID 
- Author_name, 
- books: [ books_ID ]

On peut inclure des références vers les auteurs dans la collection livre dans un tableau.

On peut aussi inclure directement les livres dans la collection auteur mais la contrainte est pour les livres qui ont plusieurs auteurs.

Dans le cas d’étudiants et de professeurs:

Un professeur possède beaucoup d’étudiants, on peut utiliser des documents intégrés en incluant un tableau de professeur dans la collection d’étudiants. Ce schéma peut poser problème si des étudiants sont aussi des professeurs.

Index multi-clé

Permet d’utiliser des index pour des relations plusieurs vers plusieurs.

Si on considère 2 collections telles que:

Students: 
- _ID, 
- name, 
- teachers: [ teacher_ID ] (tableau de teacher ID)
Teachers: 
- _ID 
- name,

Si on veut lister les professeurs d’un étudiant, on peut le faire directement. En revanche, si on veut les étudiants d’un professeur, on aura besoin d’un index multi-clé.

db.students.ensureIndex({ 'teachers':1 })

Pour avoir tous les étudiants d’un professeur:

db.students.find({ 'teachers': { $all: [ 0, 1 ] } })

Pour savoir comment l’index est utilisé:

db.students.ensureIndex({ 'teachers':1 }).explain()

Avantages d’utiliser des documents intégrés

– Permet d’améliorer les performances en lecture
– Effectuer un seul aller-retour avec la base de données
La base utilise des disques durs dont le temps d’accès est long mais qui ont une bande passante élevée: on met du temps à attendre l’information mais on peut charger rapidement beaucoup d’information en même temps. Il est donc préférable d’avoir des informations rapprochées pour les charger d’un coup.

Arbres

Par exemple, si on considère des produits et des catégories. Les catégories appartiennent à d’autres categories.
On peut proposer ce schéma:

Products: 
- Category, 
- Product_name
Category: 
- _ID 
- Category_name 
- ancestors: [ category_ID ]

Ainsi tous les ascendants de la categorie se trouvent dans le tableau “ancestors”.

Si on veut les descendants d’un categorie, il suffit de faire:

db.category.find({ ancestors: 35 })

IQueryable vs IEnumerable

Différences entre IQueryable et IEnumerable

Pour effectuer des requêtes Linq, on peut choisir des structures de liste satisfaisant une des deux interfaces IQueryable ou IEnumerable. IQueryable dérive de IEnumerable.

Caractéristiques de IEnumerable:

  • plus appropriée pour la manipulation de listes (listes génériques, Arrays etc…),
  • permet de parcourir une liste dans le sens normal,
  • exécute la requête en base, charge ensuite les données en mémoire puis effectue un filtre sur les données,
  • plus intéressant pour Linq to objects et Linq to XML
  • ne supporte pas les requête personnalisées,
  • ne supporte pas l’initialisation tardive (i.e. Lazy loading) => cette interface n’est pas adaptée pour le “paging”.

Caractéristiques de IQueryable

  • permet de parcourir une liste dans le sens normal (pas de retour en arrière et pas de déplacements entre les éléments),
  • exécute les requêtes dans les serveurs SQL,
  • est appropriée pour exécuter des requêtes sur des bases de données déportées.
  • est plus appropriée pour Linq to SQL,
  • supporte les requêtes personnalisées avec CreateQuery() et Execute(),
  • supporte l’initialisation tardive (Lazy loading) => est adapté au “paging”.

Le garbage collector en .NET en 10 min

Appel au Garbage Collector

Un certain nombre de fonctions permettent d’exécuter le Garbage Collector.
GC.Collect() permet de forcer l’exécution du GC. Un paramètre permet d’indiquer la génération des objets pour lesquelles le GC sera exécutée. La méthode ne certifie pas de l’exécution immédiate du GC, elle permet juste d’indiquer au GC qu’il devra s’exécuter.
GC.GetGeneration(object) permet de connaître la génération d’un objet.
GC.WaitForPendingFinalizers() stoppe le thread en cours jusqu’à ce que toutes les fonctions “finalizers” ont terminés de vider leur file d’attente.
GC.KeepAlive(hr): permet d’indiquer au GC qu’un objet doit être maintenu vivant et ne doit pas être supprimé. La fonction doit être placée à la fin de la partie où on désire qu’il reste vivant:

ObjectType obj = new ObjectType(); 
utilisation de l'objet dans du code..... 
… autre code …. 
GC.KeepAlive(obj);

Cette fonction est utile si on fait appel à du code qui n’est pas lié à des objets managés, à des objets de la WinAPI ou des objets COM.

Object.Finalize()

On ne peut pas faire appel directement à cette fonction. Le GC appelle cette fonction pour libérer les ressources occupées par l’objet. On peut cependant guider l’exécution du GC:
GC.SuppressFinalize() empêchera au GC d’exécuter Finalize().
GC.ReRegisterForFinalize() pour rajouter l’objet dans la liste des objets pour lesquels le GC doit exécuter Finalize().

L’implémentation correspondant à Finalize() dans un objet est le destructeur.

Les objets ayant une implémentation de Finalize() prennent plus de temps pour être détruit. Quand un objet n’est plus accessible par d’autres objets managés, le GC vérifie s’il contient une implémentation de la fonction Finalize():
– s’il ne possède pas une implémentation de Finalize(), l’objet est marqué pour être collecté et sa mémoire est requisitionnée par le GC.
– s’il possède une implémentation de Finalize(), l’objet est marqué pour être finalisé mais il ne sera pas marqué pour être collecté. La méthode Finalize() sera exécutée à la fin de l’exécution du GC. Quand cette méthode sera exécutée, l’objet sera marqué pour être collecté. Au passage suivant du GC, l’objet sera effectivement détruit. Il faut donc 2 passages du GC.

http://msdn.microsoft.com/fr-fr/library/system.object.finalize%28v=vs.110%29.aspx

Génération des objets

Génération 0: objet à courte durée de vie. Le GC s’exécute souvent pour ces objets. Les nouveaux objets sont de génération 0 sauf les objets de grande tailles (qui sont de génération 2). Les objets de génération 0 pour lesquels le GC est appelé seront automatiquement détruits.
Génération 1: c’est un buffer entre les objets de génération 0 et les objets de génération 2 c’est-à-dire entre les objets à courte durée de vie et les objets à longue durée de vie.
Génération 2: ce sont les objets à longue durée de vie. Les objets statiques font parties de cette génération.

Les objets peuvent passer à une génération supérieure (promotion) si ils ont survécu au passage du GC. Les objets qui sont à la génération 2 restent dans cette génération si ils ont survécu au passage du GC. Plus il y aura des objets de génération 2 et plus il sera difficile pour les nouveaux objets d’être de la génération 2. Ainsi on empêche on évite que la génération 2 ne devienne trop grosse et que le GC prenne trop de temps pour s’exécuter.

Exécution du GC

Au demarrage du processus:
Le Garbage Collector alloue 16 ou 32 mo, il va décider de la taille de la génération 0. Ensuite, il donne la main à l’exécution du code.
L’allocation des objets peut se fait jusqu’à ce que la génération 0 soit rempli, ensuite le GC commence son exécution:
Phase de marquage où le GC marque les objets vivants
Réallocation des références des objets pour qu’ils soient compactés
Phase de compactage: le GC récupère l’espace où les objets morts se trouvaient et déplacent les objets qui ont survécus vers un emplacement à la fin du segment de mémoire allouée. Les objets de la génération 2 peuvent être déplacés vers des segments plus anciens. La plupart du temps, les objets de grandes tailles ne sont pas compactés car l’impact sur les performances seront trop importants. Il est possible de forcer le compactage des objets de grandes tailles en faisant appel à:

GCSettings.LargeObjectHeapCompactionMode

Au fur et à mesure, il va créer la génération 2 et déplacer les objets de la génération 1 à la génération 2 etc…

Remarque importante: le GC maîtrise bien la gestion des objets de génération 0 et 1 mais il maîtrise mal les objets qui sont dans la génération 2.

Le GC utilise les éléments suivants pour déterminer si les objets sont vivants ou non:
– Les éléments racines fournis par le JIT (Just-In-Time compiler): les paramètres des méthodes, les membres privés d’une classe, les registres du CPU.
– Les objets managés (GC handles): ces objets sont alloués par l’utilisateur ou par le CLR.
– Les données statiques: les objets statiques dans le domaine d’application pouvant référencés d’autres objets et peuvent permettre à d’autres threads d’être suspendus.

Pendant l’exécution du GC, les autres threads sont suspendus.

Points faibles du GC

– il met en pause l’exécution de tous les threads pour s’exécuter,
– un seul thread fait tout le travail,
– le marquage et le compactage de la génération 2 est long.

Manipulation de ressources non managées

La libération des objets non managées doivent être effectuée explicitement car le GC ne traite que les objets managés.

http://msdn.microsoft.com/en-us/library/ee787088%28v=vs.110%29.aspx

Weak references

Il est possible de créer une weak reference sur un objet de façon à permettre sa suppression dans certaines conditions. Ainsi, pour des objets de grandes tailles qui ne sont pas utilisés réguliérement, il est possible de rajouter une weak reference. Cette reference permettra d’avoir un lien assez faible pour retrouver la valeur de l’objet en cas de besoin et pas trop fort pour qu’en cas de besoin de mémoire le GC puisse le supprimer et réquisitionner la mémoire qu’il occupe.
La condition pour utiliser une weak reference est d’utiliser un objet de grande taille et qui n’est pas utilisé tout le temps.

Il existe 2 types de weak references:
Short: la cible de la référence est nulle si il a été réquisitionné par le GC. C’est la configuration par défaut.
Long: une longue weak reference est retenue lorsque la méthode Finalize de l’objet a été exécutée. L’objet pourra être recréé mais son état n’est pas prévisible. En fait, une fois que la méthode Finalize a été exécutée, l’objet peut être supprimé à tout moment en cas de besoin.


http://msdn.microsoft.com/en-us/library/ms404247%28v=vs.110%29.aspx

http://msdn.microsoft.com/en-us/library/system.weakreference%28v=vs.110%29.aspx

Notifications du GC

On peut recevoir des notifications du GC pour savoir quand il sera exécuté et quand il a terminé son exécution. Ces notifications peuvent être utiles pour des applications manipulant des données de grande taille et où les exécutions du GC peuvent poser problème.

Les fonctions à utiliser sont:
GC.RegisterForFullGCNotification(): pour indiquer que l’on souhaite être notifié. Si on indique un “maxGenerationThreshold” élevé, le seuil de notification sera élevé donc l’exécution du GC se fera plus tardivement et on recevra la notification plus tôt. Si le seuil est trop élevé, il faudra attendre longtemps avant qu’une nouvelle exécution se fasse.
Si le seuil est bas, la probabilité sera élevé que l’exécution du GC se fasse tôt et que la notification soit tardive.
GC.WaitForFullGCApproach(): permet d’indiquer que l’exécution potentiellement bloquante du GC est imminente.
GC.WaitForFullGCComplete(): permet d’indiquer que l’exécution bloquante du GC est terminée.
GC.CancelFullGCNotification(): permet d’indiquer que l’on ne souhaite plus être notifié pour les exécutions du GC.

Toutes ces méthodes doivent être utilisées pour que les notifications se fassent correctement. Il faut de plus vérifier le statut des notifications remontées par “WaitForGCApproach” et “WaitForGCComplete”.

http://msdn.microsoft.com/fr-fr/library/cc713687%28v=vs.110%29.aspx
http://www.abhisheksur.com/2010/08/garbage-collection-notifications-in-net.html

Mode d’exécution du GC

Configuration du GC sur un serveur

L’exécution du GC se fait sur plusieurs threads. Chaque thread s’exécute de façon non-concurrente.

Configuration du GC sur un poste de travail

Il existe qu’un seul thread pour le GC. Plusieurs configurations:
Concurrent: configuration par défaut. Le GC est exécuté toujours sur un thread à part qui marque constamment les objets à supprimer quand l’application est en cours d’exécution. Le GC fait le choix ou non de compacter les données en mémoire en fonction des performances. Le compactage induit la suspension des autres threads mais cette suspension est invisible sur l’interface. Cette configuration permet de meilleurs performances pour les applications avec une GUI.
Non concurrent: le thread du GC est inactif jusqu’à son exécution. Quand l’exécution commence, le GC marque tous les objets, libère la mémoire et la compacte. Les autres threads sont suspendus pendant l’opération. L’application peut ne pas réagir pendant un instant lors de l’exécution du GC.

Il est possible de modifier la configuration dans le fichier de configuration de l’application: le paramètre est gcConcurrent.

Déboguer à distance dans Visual Studio

Pour déboguer à distance, il faut:

  • que les sources soient les mêmes que le code exécutées
  • configurer le parefeu ou le désactiver pour l’accès à la machine hôte à distance
  • que les fichiers PDB soient présents : le répertoire de ces fichiers peut être ajouté dans Tools => Options => Debugging => Symbols
  • que le debugguer à distance Visual soit installé sur la machine hôte (où se trouvent l’exécutable; Visual et le code se trouvant sur la machine cliente).

On peut télécharger le Remote Debugger à l’adresse suivante:
http://www.microsoft.com/fr-fr/download/details.aspx?id=475

Si l’installation ne se passe pas correctement, il suffit de copier les fichiers qui se trouvent sur la machine cliente dans:
C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\Remote Debugger

Authentification

Il faut privilégier une authentification plutôt que l’absence d’authentification. L’authentification Windows nécessite d’avoir un compte administrateur configuré sur la machine hôte avec les mêmes données d’authentification que la machine cliente.

Création d’un compte utilisateur sur la machine hôte

Il faut créer un compte utilisateur sur la machine hôte avec les mêmes données d’authentification que sur la machine cliente pour pouvoir utiliser l’authentification Windows. Pour créer un nouvel utilisateur, il faut:

  • Aller dans Gestion de l’ordinateur: Menu Demarrer => clique droit sur ordinateur => Gérer ou Panneau de configuration => Système et sécurité => Outils d’administration => Gestion de l’ordinateur.
  • Dans Utilisateurs et groupes => Utilisateurs, ajouter un utilisateur avec le même nom que la machine cliente
  • L’utilisateur créé doit faire partie du groupe Administrateur.

Vérifier les éléments de sécurité sur la machine hôte

Aller dans Panneau de configuration => Système et sécurité => Outils d’administration => Stratégie de sécurité locale. Dans la fenêtre, dépliser “Stratégie locale”, puis Options de sécurité. Ensuite, il faut configurer “Accès réseau: modèle de partage et de sécurité pour les comptes locaux” à “Classique – les utilisateurs locaux s’authentifient eux-mêmes”.

Débogage à distance

Remote debugger

Il faut lancer le remote debugger sur la machine hôte avec les droits de l’utilisateur nouvellement créé. Il est préférable de faire le lancement à partir d’une ligne de commande lancée en tant que le nouvel utilisateur en faisant:

runas /user:[nouvel utilisateur] "[Chemin de msvsmon.exe]"

puis configurer l’authentification dans:
Options => Options. Il faut sélectionner “Authentification Windows”.

Visual Studio

Attacher au processus correspondant à l’exécutable sur la machine hôte: Debug => Attach to process. L’exécutable sur la machine hôte doit avoir été lancé en tant que le nouvel utilisateur.
Sélectionner les paramètres suivants:

  • Transport: Default
  • Qualifier [nouvel utilisateur]@[nom machine hôte]
  • puis sélectionner l’application à débuger.

Gestion de la mémoire en .NET en 5 min

Partage de la mémoire en C#

La mémoire utilisée par un processus est divisée en 2 parties: la mémoire privée et la mémoire partagée.
Dans l’environnement .NET, le compilateur JIT compile le code pour chaque assembly séparément. Pour permettre le partage du code, il faut précompiler le code en utilisant NGEN. Ensuite il faut que les processus soient exécutés dans le même AppDomain. Enfin il faut indiquer au compilateur JIT que le code doit être partagé est décorant les fonctions avec l’attribut LoaderOptimization:

[LoaderOptimization(LoaderOptimization.MultiDomain)] 
static void Main(string[] args)

Jitter (JIT)

Le jitter (just-in-time) permet de transformer le code IL des applications en code assembleur. .NET est donc une technologie semi-interprétée. Il effectue cette compilation à la demande quand on fait appel aux assemblies. La compilation est effectuée une seule fois, à la première utilisation.

Points faibles

L’inconvénient majeur de cette technologie est que ça peut poser problème pour les applications qui ont une forte contrainte de démarrage rapide.

Multicore JIT

Depuis le framework 4.5, on va permettre d’optimiser le fonctionnement du JIT en étudiant son fonctionnement et d’enregistrer dans un fichier les décisions du jitter ainsi que la liste des fonctions à compiler. Ainsi au premier démarrage de l’application, le jitter sera optimisé et démarrera plus rapidement en lançant la compilation de certaines assemblies sur tous les processeurs.
Pour effectuer ce traitement, il suffit de rajouter dans le main:

ProfileOptimization.SetProfileRoot(@"C:\Startup"); 
ProfileOptimization.StartProfile("Startup.Profile");

Compiler le code en avance et MPGO

On peut compiler le code en avance en utilisant NGEN.exe. Cependant quand on compile en avance, le code est moins performant que s’il était compilé par le jitter. Ceci s’explique par le fait qu’à l’exécution, le jitter sait quelles sont les assemblies pour lesquelles il faut faire des optimisations puisque qu’il effectue ces modifications à la volée. Le Framework 4.5 permet de répondre à ce problème:
– en étudiant le fonctionnement du jitter pendant une exécution,
– en fournissant à NGEN.exe le résultat de cette étude,
– en compilant en avance en utilisant NGEN.exe.
Le composant qui permet cette optimisation est MPGO: Managed Profile Guided Optimization.
Pour l’utiliser:

Mpgo.exe -scenario Toto.exe -OutDir .\Data -AssemblyList Toto.exe

Pour l’instant l’optimisation faite par MPGO n’est pas au niveau de celle du jitter mais elle va s’améliorer dans les versions futures. L’optimization qui est faite actuellement permet de placer dans les mêmes pages mémoire des fonctions et des données qui s’appellent souvent entre elles (évite de faire appel à des objets dans des pages mémoire différentes qui peuvent éventuellement être swapées etc…).

Gestion des objets pour le Garbage Collector

Suivant leur taille, les objets sont gérés différemment par le Garbage Collector de .NET:
– si l’objet a une taille inférieure à 85000 bytes: il sera placé dans la pile CLR. Les piles de ce type sont compactées de temps en temps, ce qui entraîne Windows a stocké ces objets dans la mémoire physique. Ainsi on intervient directement dans la mémoire physique. Donc si l’application possède beaucoup de petits objets, on va beaucoup intervenir dans la mémoire physique sans utiliser la pile. Il faut donc éviter d’utiliser de petits objets.
– si l’objet a une taille supérieure à 85000 bytes: cet objet est placé dans la pile spécialisée pour les objets larges et il ne sera jamais déplacé par le Garbage Collector. Ainsi on ne va jamais intervenir dans la mémoire physique tant que l’objet ne dépasse pas 2gb pour un processus 32 bits ou que la page soit pleine.

http://geekswithblogs.net/akraus1/archive/2008/11/30/127475.aspx

Pile et tas managé

Les termes anglais sont “Stack” pour pile et “Managed Heap” pour tas managé.

Ces deux structures sont utilisées pour stocker:
pile: les variables locales (stockage par valeur pour les types valeur comme les struct et les énumérations) mais aussi les adresses vers les objets stockés dans le tas managé. Les adresses sont des variables référence.
tas managé: les instances de classe.

Pile (stack)

Un espace dans la pile est alloué lorsqu’on entre dans une méthode.
Les variables sont stockées dans cette espace pendant l’exécution de la méthode. Quand on sort de la méthode, l’espace est supprimé et les variables qui s’y trouvent sont perdues (seules les variables de type valeur et les références vers les instances dans le tas managé sont stockés dans la pile).

Passage de variables dans une fonction

Quand on passe une variable dans une fonction:
les variables de type valeur sont copiés dans un nouvelle espace de la pile. Quand cet espace est supprimé, les variables sont perdues.
pour les variables de type référence, seules les références vers les instances sont copiées. Quand l’espace alloué à la fonction est supprimé, les instances dans le tas managé ne sont pas supprimées.

Tas managé (managed heap)

Il contient les instances des objets de type référence. Comme son nom l’indique, la libération des objets dans cette structure est gérée par le garbage collector.

http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx

Linq en 15 min

Pour qu’on puisse utiliser une source de données avec Linq, il faut que la liste prenne en charge IEnumerable ou IQueryable (IEnumerable se suffit pas, il faut IEnumerable). Linq permet d’effectuer des requêtes sur des types très différents de données. Au moyen de “providers”, on peut effectuer des requêtes sur des fichiers XML avec “Linq to XML”; faire des requêtes sur un SQL server avec “Linq to SQL” ou faire des requêtes sur des entités avec “Linq to Entities”.

Syntaxe

On va s’intéresser à la syntaxe générale de Linq pour les requêtes sur des IEnumerable. Pour que Linq soit utilisable, il faut que la structure de données prenne en charge IEnumerable, si elle prends en charge seulement IEnumerable, va ne sera pas suffisant. La syntaxe générale est:

IEnumerable result = 
       from [identifiant local] in [structure de données] 
       where [condition sur une propriété de l'identifiant] 
       select [propriété de l'identifiant à retourner]

Le résultat des requêtes Linq peuvent être: – IEnumerable, – iEnumerable ou – iQueryable

Exécution

L’exécution des requêtes n’est pas immédiate. Elle sera effectué lorsque la structure du résultat sera utilisée dans une boucle Foreach. Pour forcer l’exécution de la requête, on peut utiliser des fonctions d’agrégation: Count(), Max(), Min() ou Average(). La syntaxe pour ces fonctions est:

var evenNumQuery = 
    from num in numbers 
    where (num % 2) == 0 
    select num; 
int evenNumCount = evenNumQuery.Count();

Si on doit retourner une liste, il faut utiliser: ToList ou ToArray. La syntaxe est alors:

List numQuery2 = 
    (from num in numbers 
     where (num % 2) == 0 
     select num).ToList(); 
// or like this: 
// numQuery3 is still an int[] 
var numQuery3 = 
    (from num in numbers 
     where (num % 2) == 0 
     select num).ToArray();

Clause SELECT

Quelques exemples de clause SELECT: Méthode d’extension Linq exécutée sur une liste:

ContactInfo cInfo = 
     (from ci in app.contactList 
     where ci.ID == id 
     select ci) 
     .FirstOrDefault();

Retour d’un élément de la liste directement:

IEnumerable studentQuery1 = 
     from student in app.students 
     where student.ID > 111 
     select student;

Retour d’un membre d’un élément de la liste:

IEnumerable studentQuery2 = 
     from student in app.students 
     where student.ID > 111 
     select student.Last; 
 
IEnumerable studentQuery4 = 
     from student in app.students 
     where student.ID > 111 
     select student.Scores[0]; 

IEnumerable studentQuery5 = 
     from student in app.students 
     where student.ID > 111 
     select student.Scores[0] * 1.1;

Exécution d’une fonction dans l’élément retourné:

IEnumerable studentQuery3 = 
     from student in app.students 
     where student.ID > 111 
     select student.GetContactInfo(app, student.ID);
 
IEnumerable studentQuery6 = 
     from student in app.students 
     where student.ID > 111 
     select student.Scores.Average();

Retour d’un nouvel élément avec un initialiseur d’objet:

var studentQuery7 = 
     from student in app.students 
     where student.ID > 111 
     select new { student.First, student.Last };
 
IEnumerable studentQuery8 = 
     from student in app.students 
     where student.ID > 111 
     select new ScoreInfo 
     { 
         Average = student.Scores.Average(), 
         ID = student.ID 
     };

Utilisation d’une jointure:

IEnumerable studentQuery9 = 
     from student in app.students 
     where student.Scores.Average() > 85 
     join ci in app.contactList on student.ID equals ci.ID 
     select ci;

Lien MSDN

Clause FROM

Il est possible d’utiliser des “From” imbriqués qui correspondent à 2 boucles “foreach”: une première boucle pour la liste d’élément et une 2ème pour une liste dans la première liste:

var scoreQuery = 
     from student in students 
     from score in student.Scores 
     where score > 90 
     select new { Last = student.LastName, score };

Le “select” permet de retourner des éléments parmi ceux manipulés dans les 2 from. On peut faire des clauses from imbriquées pour des listes qui n’ont pas de lien entres elles:

char[] upperCase = { 'A', 'B', 'C' }; 
char[] lowerCase = { 'x', 'y', 'z' }; 
 
var joinQuery1 = 
     from upper in upperCase 
     from lower in lowerCase 
     select new { upper, lower }; 
 
var joinQuery2 = 
     from lower in lowerCase 
     where lower != 'x' 
     from upper in upperCase 
     select new { lower, upper };

Clause WHERE

Opérateurs logiques

Dans une clause WHERE, on peut spécifier plusieurs conditions séparées par des opérateurs logiques ET et OU mais ATTENTION il faut utiliser: – “&&” pour ET – “||” pour OU Par exemple:

var queryLondonCustomers =  
     from cust in customers  
     where cust.City=="London" && cust.Name == "Devon" 
     select cust;

Utilisation de fonctions

On peut utiliser une fonction dans la clause WHERE:

var queryEvenNums =  
     from num in numbers  
     where IsEven(num)  
     select num;

Lien MSDN

Clause JOIN

Opérateur de comparaison “EQUALS”

L’opérateur de comparaison utilisé pour les jointures est seulement “EQUALS”. On ne peut pas utiliser “supérieur à” ou “différent de” ce qui limite beaucoup fonctionnellement l’intérêt de linq.

Jointure interne

Seuls les éléments communs aux 2 listes sont retournés:

var innerJoinQuery = 
     from category in categories 
     join prod in products on category.ID equals prod.CategoryID 
     select new { ProductName = prod.Name, Category = category.Name };

Jointure avec une clé composite:

On peut appliquer l’opérateur d’égalité sur une structure plus complexe:

IEnumerable query =  
     from employee in employees  
     join student in students on new { employee.FirstName, employee.LastName } 
     equals new { student.FirstName, student.LastName }  
     select employee.FirstName + " " + employee.LastName;

Jointure multiple

Comme pour les autres opérateurs, on peut utiliser plusieurs clause JOIN pour effectuer une jointure multiple:

var query =  
     from person in people  
     join cat in cats on person equals cat.Owner  
     join dog in dogs on new { Owner = person, Letter = cat.Name.Substring(0, 1)} 
     equals new { dog.Owner, Letter = dog.Name.Substring(0, 1) }  
     select new { CatName = cat.Name, DogName = dog.Name };

Jointure groupée

Ce type de jointure n’a pas d’équivalent en requête relationnelle, elle permet d’identifier le résultat d’une jointure dans une variable pour être facilement utilisable dans la clause SÉLECT ou dans une nouvelle requête:

var innerGroupJoinQuery = 
    from category in categories 
    join prod in products on category.ID equals prod.CategoryID into prodGroup 
    select new { CategoryName = category.Name, Products = prodGroup };

Pour une nouvelle requête:

var innerGroupJoinQuery2 = 
    from category in categories 
    join prod in products on category.ID equals prod.CategoryID into prodGroup 
    from prod2 in prodGroup 
    where prod2.UnitPrice > 2.50M 
    select prod2;

Jointure externe gauche

On retourne les éléments de la source de gauche même s’ils n’existent pas dans la source de droite. Il faut utiliser la méthode “DefaultIfEmpty” avec une jointure groupée pour indiquer les valeurs à retourner si la source de droite ne contient pas de valeurs.

var leftOuterJoinQuery = 
    from category in categories 
    join prod in products on category.ID equals prod.CategoryID into prodGroup 
    from item in prodGroup.DefaultIfEmpty(new Product { Name = String.Empty, CategoryID = 0 }) 
    select new { CatName = category.Name, ProdName = item.Name };

Lien MSDN

Non equi-jointure

Une jointure n’est possible qu’avec un opérateur d’égalité. Pour utiliser un autre opérateur, il existe quelques astuces en utilisant 2 clauses FROM:

var nonEquijoinQuery = 
     from p in products 
     let catIds = from c in categories 
               select c.ID 
     where catIds.Contains(p.CategoryID) == true 
     select new { Product = p.Name, CategoryID = p.CategoryID };

Lien MSDN

Mot clé ORDERBY

Permet d’ordonner les résultats dans la structure IEnumerable. On utilise: – “ascending” pour l’ordre croissant, – “descending” pour l’ordre décroissant.

IEnumerable sortDescendingQuery =  
     from w in fruits  
     orderby w descending  
     select w;

On peut ordonner suivant plusieurs éléments:

IEnumerable sortedStudents =  
from student in students  
orderby student.Last ascending, student.First ascending  
select student;

Lien MSDN

Clause GROUP…BY

“Group…by” permet de grouper les éléments mais attention la structure de sortie n’est plus une IEnumerable mais IEnumerable<IGrouping>. TGROUPING est le type ayant permis de grouper les éléments. IGrouping est comme un dictionnaire. ATTENTION: ce mot clé s’utilise avec la syntaxe:

GROUP [identifiant local] BY [élément utilisé pour grouper]

Il n’est pas nécessaire d’utiliser une clause SELECT lorsqu’on utilise le mot clé GROUP…BY:

var queryCustomersByCity =  
     from cust in customers  
     group cust by cust.City;

Utilisation avec INTO:

Into permet d’utiliser une variable qui sera utilisée ensuite dans une autre clause WHERE ou SELECT:

var custQuery =  
     from cust in customers  
     group cust by cust.City into custGroup  
     where custGroup.Count() > 2  
     orderby custGroup.Key  
     select custGroup;

Lien MSDN

Mot clé LET

LET permet de définir une variable qui pourra être utilisée dans une 2e clause FROM, dans une clause SELECT ou dans une clause WHERE:

var earlyBirdQuery =  
     from sentence in strings 
     let words = sentence.Split(' ') 
     from word in words 
     let w = word.ToLower() 
     where w[0] == 'a' || w[0] == 'e' || w[0] == 'i' || w[0] == 'o' || w[0] == 'u' 
     select word;

Lien MSDN

Commandes utiles Package Manager Console

Installer un package

Install-package [nom du package] {-Version [numéro de version]}

Pour une version prérelease:

Install-package [nom du package] {-Version [numéro de version]} -Pre

Supprimer un package

Uninstall-package [nom du package]

Lister les packages dans la solution

get-package {[nom du package]}

Lister les packages disponibles dans les repositories

get-package {[nom du package]} -ListAvailable

Lister les version d’un package disponible dans les repositories

get-package [nom du package] -ListAvailable -AllVersions

Raccourcis clavier utiles Visual Studio

Quelques raccourcis clavier utiles

Ctrl + Shift + O: ouvrir un projet
Ctrl + Shift + N: nouveau projet
Ctrl + Shift + B: builder la solution
Ctrl + ,: ouvrir un fichier en fonction du nom de la classe

A utiliser dans l’éditeur

Ctrl + K, Ctrl + C: commenter la sélection
Ctrl + K, Ctrl + U: décommenter la sélection
Ctrl + M, Ctrl + O: replier tous les noeuds
Ctrl + M, Ctrl + L: déplier tous les noeuds
Alt + Shift + F10: suggérer les dépendances pour un objet
Shift + F12: trouver les utilisations d’un objet
Ctrl + F12: voir les implémentations d’un objet déclaré dans une interface

En rapport avec l’exécution

F5: lancer l’exécution
Shift + F5: arrêter l’exécution

Quelques options utiles

Pour sélectionner le fichier en cours de lecture dans le “Solution Explorer”

  1. Cliquer sur Tools,
  2. Cliquer sur Options,
  3. Déplier Projects and Solutions
  4. Sélectionner General
  5. Cocher “Track Active Item In Solution Explorer”

Ajouter un token pour des commentaires personnalisés

Ces commentaires sont facilement visibles de la Task List (View -> Task List)

  1. Cliquer sur Tools,
  2. Cliquer sur Options,
  3. Déplier Environment
  4. Rajouter un type de commentaire dans Task List

Afficher les numéros de lignes dans l’éditeur

  1. Cliquer sur Tools,
  2. Cliquer sur Options,
  3. Déplier Text editor
  4. Sélectionner All languages
  5. Dans Display cocher “Line numbers”

Pour que la fenêtre principale reste devant quand on utilise les fenêtres flottantes

Dans Visual Studio 2012, 2013 et 2015:

  1. Cliquer sur Tools,
  2. Cliquer sur Options,
  3. Déplier Environment
  4. Sélectionner Tabs and Windows
  5. Dans Tab Well cocher “Floating tab wells always stay on top of the main window”

Désactiver les “Diagnostics Tools” en debug

Dans visual 2015:

  1. Cliquer sur Tools,
  2. Cliquer sur Options,
  3. Déplier Debugging
  4. Dans General décocher “Enable Diagnostics Tools while debugging”