Affichage des articles dont le libellé est Expressions Lambda. Afficher tous les articles
Affichage des articles dont le libellé est Expressions Lambda. Afficher tous les articles

Introduction à LINQ To SQL par l'exemple

L'article qui suit a pour but la manipulation des données en base avec LINQ To SQL.

Nous allons présenter une série de diverses opérations CRUD (Create Read Update Delete)
appliquées sur notre base de données ECOLE.

1. Opérations de lecture :

Dans cette partie, on répondra à chaque question de la manière suivante, dans l'ordre :
  • Requête SQL
  • "Expression query" de LINQ
  • Opérateurs LINQ (méthodes d'extention) de la classe Queryable, et expressions lambda : "Lambda query"

Q1. Obtenir la liste des noms, prénoms et date de naissance de tous les élèves.
SELECT nom, prenom, date_naissance  
FROM ELEVES
var listEleves = from el in dc.ELEVEs
                 select new { el.NOM, el.PRENOM, el.DATE_NAISSANCE };

var listEleves = dc.ELEVEs.Select(el => new { el.NOM, el.PRENOM, el.DATE_NAISSANCE });

Q2. Obtenir tous les renseignements sur toutes les activités.
SELECT * FROM ACTIVITES
var listActivites = from a in dc.ACTIVITEs
                    select a;

var listActivites = dc.ACTIVITEs.Select(a => a);

Q3. Obtenir la liste des spécialités des professeurs.
SELECT
DISTINCT specialite
FROM PROFESSEURS
WHERE specialite IS NOT NULL
var listSpec = from p in dc.PROFESSEURs
               where p.SPECIALITE != null
               select new { p.SPECIALITE };

var listSpec = dc.PROFESSEURs
               .Where(p => p.SPECIALITE != null)
               .Select(p => p.SPECIALITE)
               .Distinct();

Q4. Obtenir le nom et le prénom des élèves pesant moins de 45 kilos et inscrits en 1ére année ou des élèves inscrits en 2ème année.
SELECT nom, prenom
FROM ELEVES
WHERE (poids < 45 AND annee =" 1) OR annee = 2
var listEleves = from el in dc.ELEVEs
                 where ((el.POIDS < 45 && el.ANNEE == 1) || el.ANNEE == 2)
                 select new { el.NOM, el.PRENOM };

var listEleves = dc.ELEVEs
                 .Where(el => ((el.POIDS < 45 && el.ANNEE == 1) || el.ANNEE == 2))
                 .Select(el => new { el.NOM, el.PRENOM } );

Q5. Obtenir le nom des professeurs dont la spécialité est 'poésie' ou 'sql'.
SELECT nom
FROM PROFESSEURS
WHERE specialite IN ('poésie', 'sql')
var listProfesseurs = from p in dc.PROFESSEURs
                      where (p.SPECIALITE.Equals("poésie") || p.SPECIALITE.Equals("sql"))
                      select p.NOM;

var listProfesseurs = dc.PROFESSEURs
                      .Where(p => (p.SPECIALITE.Equals("poésie")
                                    || p.SPECIALITE.Equals("sql")))
                      .Select(p => p.NOM);

Q6. Obtenir le nom des élèves dont le nom commence par 'L'.
SELECT nom
FROM ELEVES
WHERE nom LIKE'L%'

var listEleves = from el in dc.ELEVEs
                 where el.NOM.StartsWith("L")
                 select el.NOM;

var listEleves = dc.ELEVEs.Where(el => el.NOM.StartsWith("L")).Select(el => el.NOM);

Q7. Obtenir pour chaque professeur, son nom et sa spécialité.
SELECT nom, ISNULL(specialite, '****') AS specialite
FROM professeurs
var listSpec = from p in dc.PROFESSEURs
               select new { specialite = (p.SPECIALITE == null)?"****":p.SPECIALITE };

var listSpec = dc.PROFESSEURs.Select(p => (p.SPECIALITE == null)?"****":p.SPECIALITE);

Q8. Obtenir le nom et prenom d'eleves et leur résultats par cours.
SELECT nom, ISNULL(specialite, '****') AS specialite
FROM professeurs
var listResults = from el in dc.ELEVEs
                  join r in dc.RESULTATs on el.NUM_ELEVE equals r.NUM_ELEVE
                  join c in dc.COURs on r.NUM_COURS equals c.NUM_COURS
                  select new { el.NOM, el.PRENOM, nomCour = c.NOM, r.POINTS };

var listResults = dc.ELEVEs.Join(dc.RESULTATs, el => el.NUM_ELEVE, r => r.NUM_ELEVE,
                                (el, r) => new { el.NOM, el.PRENOM, r.POINTS, r.NUM_COURS })
                           .Join(dc.COURs, tmpVar => tmpVar.NUM_COURS, c => c.NUM_COURS,
                                (tmpVar, c) => new { tmpVar.NOM, tmpVar.PRENOM, nom_cours = c.NOM, tmpVar.POINTS });

Q9. Obtenir le nombre de professeurs par specialité. Ordonnez par ordre alphabétique des spécialités.
SELECT specialite, nb_professeurs = count(*)
FROM PROFESSEURS
GROUP BY specialite
var listResult = from p in dc.PROFESSEURs
                 where p.SPECIALITE != null
                 group p by p.SPECIALITE into g
                 orderby g.Key ascending
                 select new { g.Key, nb_professeurs = g.Count() };

var listResult = dc.PROFESSEURs
                 .Where(p => p.SPECIALITE != null)
                 .GroupBy(p => p.SPECIALITE)
                 .OrderBy(g => g.Key)
                 .Select( g => new { g.Key, nb_professeurs = g.Count() });


2. Opérations de création, mise à jour et de suppression :

Q1. Créer et ajouter un nouveau professeur .NET nommé "Antonio", arrivé aujourd'hui. Son salaire de base et actuel sera de 2500000.

// création du nouveau professeur
PROFESSEUR newProf = new PROFESSEUR { NUM_PROF = 9,
                                      NOM = "Antonio",
                                      SPECIALITE = ".NET",
                                      DATE_ENTREE = DateTime.Now,
                                      SALAIRE_BASE = 2500000,
                                      SALAIRE_ACTUEL = 2500000 };

dc.PROFESSEURs.InsertOnSubmit(newProf); // ajout et prise en compte dans le DataContext
dc.SubmitChanges(); // sauvegarde effective en base de donnée

Q2. Depuis son arrivée, Antonio a eu une augmentation de 10% par rapport à son salaire actuel. Mettre à jour ces informations.

try
{
    var prof = dc.PROFESSEURs.Single(p => p.NOM == "Antonio" && p.SPECIALITE == ".NET");
    prof.SALAIRE_ACTUEL += prof.SALAIRE_ACTUEL * 0.1;
    dc.SubmitChanges();
}
catch(Exception ex){
    // Exception : Pas de professeurs .NET appelé Antonio !
}

Q3. Antonio enseigne la programmation. Ajoutez cette information dans la table CHARGE.


// récupère le professeur Antonio
var leProf = dc.PROFESSEURs.Single(p => p.NOM == "Antonio");

// récupère le cours de programmation
var leCours = dc.COURs.Single(c => c.NOM == "Programmation");

// crée une nouvealle charge
CHARGE uneCharge = new CHARGE { PROFESSEUR = leProf, COUR = leCours };

// Ajoute et sauvegarde en base la nouvelle charge
dc.CHARGEs.InsertOnSubmit(uneCharge);
dc.SubmitChanges();

Q4. Notre bon vieil ami Antonio n'enseigne plus et part finir ses jours à la retraite au soleil... Supprimer toutes les informations existantes sur Antonio.


/* Suppression des informations d'Antonio */

// récupère le professeur Antonio
var prof = dc.PROFESSEURs.Single(p => p.NOM.Equals("Antonio"));

// Prise en compte de la suppression dans le DataContext
dc.PROFESSEURs.DeleteOnSubmit(prof);

// Récupère la liste des charges d'Antonio
var listeCharges = dc.CHARGEs
                   .Where(c => c.PROFESSEUR.Equals(prof))
                   .Select(c => c);

// Prise en compte de la suppression des charges dans le DataContext
dc.CHARGEs.DeleteAllOnSubmit(listeCharges);

// Validation et Suppression effective en base de donnée :
dc.SubmitChanges();

Introduction aux Expressions Lambda


Pour comprendre l'intérêt des expressions lambda, il est d'abord intéressant de revenir sur plusieurs concepts introduits en C# depuis la version 1.0 :
  • Les délégués
  • Les méthodes anonymes
Nous allons détailler nos explications en nous basant sur un exemple concret.



L'objectif sera d'écrire une fonction qui prendra en paramètre une liste de personnes et de la filtrer sur un critère donné (âge, prénom ...) pour en afficher la liste résultante.

    Notre classe Personne :

    // une classe Personne avec 3 propriétés et accesseurs respectifs.
    class Personne
    {
        // Accesseurs : getters & setters en C# 3.0
        public int Age { get; set; }
        public string Prenom { get; set; }
        public string Nom { get; set; }

        // Une méthode affichant les infos sur une liste de personnes
        public static void displayList(List<Personne> list)
        {
            foreach(Personne p in list)
                Console.WriteLine("Nom : "+p.Nom+", Prenom : "+p.Prenom+", Age : "+p.Age);
        }
    }

    On souhaite obtenir la liste des adultes, notre critère de test sera donc l'âge ( >= 18 ans).
    Ci-dessous la définition de notre fonction displayAdultPersonne(List<personne> listPersonnes) :

    class Personne
    {
        // ...

        // Notre méthode de filtre et d'affichage
        public static void displayAdultPersonne(List<Personne> listPersonnes)
        {
            // contiendra la liste des adultes
            List<Personne> adults = new List<Personne>();

            // test sur l'âge
            foreach (Personne p in listPersonnes)
                if (p.Age >= 18) adults.Add(p);

            Personne.displayList(adults);
        }
    }

    Notre programme de test sur une liste quelconque :

    // fonction main(), point d'entrée pour nos tests.
    static void Main(string[] args)
    {
        // initialisation d'une liste quelconque de Personnes
        List<Personne> personneList = new List<Personne> {
            new Personne { Age = 10, Nom = "Dupond", Prenom = "François"},
            new Personne { Age = 21, Nom = "Dupont", Prenom = "Jean"},
            new Personne { Age = 29, Nom = "Martin", Prenom = "Sarah"},
            new Personne { Age = 17, Nom = "Petit", Prenom = "Nicolas"},
            new Personne { Age = 24, Nom = "Dubois", Prenom = "Marie"}
        };

        // Appel de la fonction displayAdultPersonne() ..
        Personne.displayAdultPersonne(personneList);

        Console.WriteLine("Appuyer Entrée pour quitter ...");
        Console.Read();
    }

    Les résultats à l'exécution :


    La première version de programme est disponible ici.

    Question :

    Maintenant, que se passe-t-il si l'on souhaite filtrer la liste de personnes sur un autre critère ?
    Par exemple, filtrer et afficher les personnes au prenom de 5 lettres ? Créer une autre méthode avec un test sur le nom ?!
    Pas très flexible comme solution ...

    Utilisation des délégués

    C'est là qu'intervient l'utilité des délégués introduits en C# 1.0.
    L'idée sera de passer un paramètre supplémentaire à notre fonction displayAdultPersonne(List<Personne> listPersonnes) : un délégué pointant sur différentes méthodes possibles. Celles-ci auront pour rôle de filtrer : sur l'âge, sur le nom ...etc des personnes.

    Modifions la classe Personne en conséquence :


    Explications :
    • 1. Création du type délégué de type myDelegate.
      Dans notre cas, le délégué pointera sur une méthode qui prendra en argument un type Personne et renverra un booléen, égalant vrai si la condition de filtre est vérifiée (la personne est bien un adulte, ou bien la personne a un prénom de 5 lettres.)
    • 2. Ajout d'un paramètre supplémentaire de type myDelegate à notre fonction displayAdultPersonne(List<Personne> listPersonnes)
    • 3. Création d'une méthode de filtre sur l'âge
    • 4. Création d'une méthode de filtre sur les prénoms contenant 5 lettres
    Pour appeller la fonction displayAdultPersonne(List<Personne> listPersonnes, myDelegate filter), on initialise notre délégué de type myDelegate avec nos méthodes de filtre isAdult(Personne p) ou bien matchNameLength(Personne p).

    Console.WriteLine("Filtre sur les personnes adultes :\n");           

    // Instanciation du délégué
    Personne.myDelegate filterAge = new Personne.myDelegate(Personne.isAdult);

    // Appel de la fonction displayAdultPersonne() avec le délégué en paramètre
    Personne.displayAdultPersonne(personneList, filterAge);

    ou bien :

    Console.WriteLine("\nFiltre sur les personnes à prenom avec 5 lettres :\n");

    // Instanciation du délégué
    Personne.myDelegate filterPrenom = new Personne.myDelegate(Personne.matchNameLength);

    // Appel de la fonction displayAdultPersonne() avec le délégué en paramètre
    Personne.displayAdultPersonne(personneList, filterPrenom);

    A l'exécution du programme, nous obtenons les résultats :


    Désormais, la fonction displayAdultPersonne n'effectue plus le rôle de filtre puisque nous l'avons paramétrée pour transmettre ce rôle à un délégué.
    Ok, nous avons rendu flexible le code. Et ce, au prix d'une complexité non négligeable.

    Nous allons simplifier tout cela pour arriver à notre objectif qui reste le même : filtrer une liste de personnes sur un critère donné.

    La version évoluée de programme est disponible ici.

    Utilisation du délégué Predicate<T>

    Au lieu de créer notre propre type délégué, nous pouvons utiliser le type Predicate<T> existant et introduit en C# 2.0 :
    delegate booléen Predicate<T>(T obj);

    Le type Predicate<T> est un délégué générique.
    Il représente une méthode qui retourne un booléen en fonction de son argument générique T en entrée.
    Notre type T ici sera le type Personne.

    La fonction displayAdultPersonne de la classe Personne devient alors :

    class Personne
    {
        // ...

        // Notre méthode d'affichage avec le délégué Predicate<T> en paramètre.
        public static void displayAdultPersonne(List<Personne> listPersonnes,
                                                    Predicate<Personne> filter)
        {
            // contiendra la liste des adultes
            List<Personne> adults = new List<Personne>();

            foreach (Personne p in listPersonnes)
                if (filter(p))
                    adults.Add(p);

            Personne.displayList(adults);
        } 

        // Méthode de filtre sur l'âge
        public static bool isAdult(Personne p)
        {
            if(p.Age >= 18)
                return true;
            else return false;
        }

        // Méthode de filtre sur les prénoms de 5 lettres
        public static bool matchNameLength(Personne p)
        {
            if (p.Prenom.Length == 5)
                return true;
            else return false;
        }

    }

    Commentaires :
    • Plus besoin de créer un type délégué spécifique (myDelegate)
    • Plus besoin d'initialiser ce délégué avec les méthodes de filtre isAdult(Personne p) ou matchNameLength(Personne p)
    • On appelle simplement notre fonction displayAdultPersonne directement avec une des méthodes de filtre en paramètre

    // Appel de la fonction displayAdultPersonne() avec le type Predicat<T> en arg
    Console.WriteLine("Filtre sur les personnes adultes :\n");
    Personne.displayAdultPersonne(personneList, Personne.isAdult);

    Console.WriteLine("\nFiltre sur les personnes à prenom avec 5 lettres :\n");
    Personne.displayAdultPersonne(personneList, Personne.matchNameLength);

    Nous obtenons les mêmes résultats que précédemment (avec l'utilisation du délégué myDelegate en moins).


    La version évoluée du programme est disponible ici.

    Utilisation des méthodes anonymes

    Pour simplifier l'utilisation des délégués, les méthodes anonymes sont apparues en C# 2.0.
    Leur intérêt est de permettre la déclaration inline de la méthode pointée par le délégué, donc sans avoir à la nommer.

    Par conséquent, il n'est plus utile de définir dans la classe Personne, les méthodes nommées isAdult(Personne p) ou matchNameLength(Personne p).

    // Appel de la fonction displayAdultPersonne avec les méthodes anonymes
    Console.WriteLine("Filtre sur les personnes adultes :\n");
    Personne.displayAdultPersonne(personneList, delegate(Personne p) { return p.Age >= 18; });

    Console.WriteLine("\nFiltre sur les personnes à prenom avec 5 lettres :\n");
    Personne.displayAdultPersonne(personneList, delegate(Personne p) { return p.Prenom.Length == 5; });

    L'appel se fait sur une seule ligne, et le code est beaucoup plus lisible pour obtenir à l'exécution les mêmes résultats que précédemment.

    La version évoluée du programme est disponible ici.

    Les lambda expressions

    Enfin, nous arrivons aux lambda expressions qui permettent de simplifier tout ce que nous avons vu jusqu'à présent.
    Pour filtrer sur notre liste de personnes, au lieu d'utiliser des délégués ou des méthodes anonymes, on peut utiliser une expression lambda équivalente.

    // Appel de la fonction displayAdultPersonne avec les lambda expressions !

    Personne.displayAdultPersonne(personneList, p => p.Age >= 18);
    Personne.displayAdultPersonne(personneList, p => p.Prenom.Length == 5);

    Ci-dessus, notre expression lambda p => p.Age >=18

    se traduit comme :

    "Pour toute Personne p, on renvoie vrai si l'âge est supérieur à 18."

    Ainsi, les expressions lambda permettent de parvenir à notre objectif par une approche déclarative plutôt que programmatique (i.e exprimer ce que l'on souhaite obternir par une série d'instructions en if, boucle for ..).

    Les expressions lambda sont des fonctions sans nom qui peuvent être utilisées partout où le type délégué est valide.

    Une expression lambda est constituée de 3 parties :
    • un ou plusieurs paramètres d'entrée
    • l'opérateur =>
    • une expression à évaluer ou une série d'instructions
    Dans notre exemple, puisque notre fonction displayAdultPersonne prend en paramètre un délégué de type Personne et renvoie un booléen :
    il est indispensable que l'expression lambda équivalent prenne en argument un type Personne et renvoie un booléen.

    Un peu plus compliqué, pour récupérer la liste des adultes dont le prénoms contiennent 5 lettres :

    Personne.displayAdultPersonne(personneList, p => p.Age >=18 && p.Prenom.Length == 5);

    La version évoluée de programme est disponible ici.

    A première vu, comprendre les expressions lambda n'est pas une chose simple.
    Derrière ce nom bizzaroïde, se cache toute la puissance de la programmation fonctionnelle.