C’est maintenant que tout commence. Nous allons apprendre à manipuler nos entités et à les persister, c’est-à-dire les enregistrer dans la base de données.
Comme la base de données est un système puissant, il nous permet de lui laisser faire un certains nombres de traitements que nous devrions théoriquement faire dans le contrôleur.
Cette partie vous présentera donc comment obtenir les données sauvegardées dans la base de données, les filtrer, comment les sauvegarder.
Nous terminerons en présentant le patron de conception "Repository" aussi appelé "Bridge", afin de rendre notre code plus propre et maintenable.
- Obtenir, filtrer, ordonner vos entités
- Ajouter une entité à la bdd
- Modifier et supprimer une entité dans la base de données
- Le patron de conception Repository
Obtenir, filtrer, ordonner vos entités
Allons donc dans notre contrôleur ArticleController.cs
.
La première étape sera d’ajouter une instance de notre contexte de données en tant qu’attribut du contrôleur.
Une fois ce lien fait, nous allons nous rendre dans la méthode List
et nous allons remplacer le vieux code par une requête à notre base de données.
Entity Framework use Linq To SQL pour fonctionner. Si vous avez déjà l’habitude de cette technologie, vous ne devriez pas être perdu.
Sinon, voici le topo :
Linq, pour Language Integrated Query, est un ensemble de bibliothèques d’extensions codées pour vous simplifier la vie lorsque vous devez manipuler des collections de données.
Ces collections de données peuvent être dans des tableaux, des listes, du XML, du JSON, ou bien une base de données !
Quoi qu’il arrive, les méthodes utilisées seront toujours les mêmes.
Obtenir la liste complète
Il n’y a pas de code plus simple : bdd.Articles.ToList();
. Et encore, le ToList()
n’est nécessaire que si vous désirez que votre vue manipule une List<Article>
au lieu d’un IEnumerable<Article>
. La différence entre les deux : la liste est totalement chargée en mémoire alors que le IEnumerable
ne fera en sorte de charger qu’un objet à la fois.
Certains cas sont plus pratiques avec une liste, donc pour rester le plus simple possible, gardons cette idée !
Obtenir une liste partielle (retour sur la pagination)
Souvenez-vous de l’ancien code :
Notre idée restera la même avec Linq. Par contre Linq ne donne pas une méthode qui fait tout à la fois.
Vous n’aurez droit qu’à deux méthodes :
Take
: permet de définir le nombre d’objet à prendre ;Skip
: permet de définir le saut à faire.
Néanmoins, avant toute chose, il faudra que vous indiquiez à EntityFramework comment ordonner les entités.
Pour s’assurer que notre liste est bien ordonnée par la date de publication (au cas où nous aurions des articles dont la publication ne s’est pas faite juste après l’écriture), il faudra utiliser la méthode OrderBy
.
Cette dernière attend en argument une fonction qui retourne le paramètre utilisé pour ordonner.
Par exemple, pour nos articles, rangeons-les par ordre alphabétique de titre :
Ainsi, avec la pagination, nous obtiendrons :
Par exemple si nous avions 50 articles en base de données, avec 5 articles par page, nous afficherions à la page 4 les articles 21, 22, 23, 24 et 25.
Mais le plus beau dans tout ça, c’est qu’on peut chaîner autant qu’on veut les Skip et les Take. Ainsi en faisant : Skip(3).Take(5).Skip(1).Take(2)
nous aurions eu le résultat suivant :
N° article | Pris |
---|---|
1 | non |
2 | non |
3 | non |
4 | oui |
5 | oui |
6 | oui |
7 | oui |
8 | oui |
9 | non |
10 | oui |
11 | oui |
Si vous voulez que votre collection respecte un ordre spécial, il faudra lui préciser l’ordre avant de faire les Skip
et Take
, sinon le système ne sait pas comment faire.
Filtrer une liste
La méthode Where
Avant de filtrer notre liste, nous allons légèrement modifier notre entité Article
et lui ajouter un paramètre booléen nommé EstPublie
.
Maintenant, notre but sera de n’afficher que les articles qui sont marqués comme publiés.
Pour cela il faut utiliser la fonction Where
qui prend en entrée un délégué qui a cette signature :
Dans le cadre de notre liste, nous pourrions donc utiliser :
Dans vos filtres, toutes les fonctions ne sont pas utilisables dès qu’on traite les chaînes de caractères, les dates…
Filtre à plusieurs conditions
Pour mettre plusieurs conditions, vous pouvez utiliser les mêmes règles que pour les embranchements if
et else if
, c’est-à-dire utiliser par exemple article.EstPublie && article.Titre.Length < 100
ou bien article.EstPublie || !string.IsNullOrEmpty(article.ThumbnailPath)
.
Néanmoins, dans une optique de lisibilité, vous pouvez remplacer le &&
par une chaîne de Where
.
N’afficher qu’une seule entité
Afficher une liste d’articles, c’est bien, mais rapidement la liste deviendra longue. Surtout, nos articles étant des textes qui peuvent être assez denses, il est souhaitable que nous ayons une page où un article sera affiché seul.
Cette page vous facilitera aussi le partage de l’article : la liste est mouvante, mais la page où seul l’article est affiché restera toujours là, à la même adresse.
![[a]] | Il est vraiment important que la page reste à la même adresse et ce même si vous changez le texte ou le titre de l’article.
Pour le cours, cette page sera créée plus tard, lorsque nous verrons les liens entre les entités car nous voulons bien sûr afficher les commentaires de l’article sur cette page. Néanmoins, cela sera un bon exercice pour vous de la coder maintenant.
Indices
- Pour sélectionner un article, il faut pouvoir l'identifier de manière unique.
- La fonction
db.VotreDBSet.FirstOrDefault(m=>m.Parametre == Valeur)
vous permet d’avoir le premier objet qui vérifie la condition ou bien null si aucun article ne convient. - Par convention, en ASP.NET MVC on aime bien appeler cette page Details.
Ajouter une entité à la bdd
Quelques précisions
Avant de montrer le code qui vous permettra de sauvegarder vos entités dans la base de données, je voudrais vous montrer plus en détail comment Entity Framework gère les données.
Comme le montre le schéma, une entité n’est pas tout de suite envoyée à la base de données. En fait pour que cela soit fait il faut que vous demandiez à la base de données de persister tous les changements en attente.
"détachés" et "supprimés" sont des états qui se ressemblent beaucoup. En fait "détaché" cela signifie que la suppression de l’entité a été persistée.
À partir de là, le système va chercher parmi vos entités celles qui sont marquées :
- créées ;
- modifiées ;
- supprimées.
Et il va mettre en place les actions adéquates.
La grande question sera donc :
Comment marquer les entités comme "créées" ?
Ajouter une entité
C’est l’action la plus simple de toutes : bdd.VotreDbSet.Add(votreEntite);
et c’est tout !
Ce qui transforme notre code d’envoi d’article en :
Modifier et supprimer une entité dans la base de données
Préparation
Ici le cas sera plus "complexe" si je puis dire. En effet, pour s’assurer que quoi qu’il arrive le changement soit bien détecté sans perte de performance (c’est-à-dire qu’on ne va pas demander au système de comparer caractère par caractère le texte de l’article pour détecter le changement), il faudra forcer un peu les choses.
- Premièrement, il faudra aller chercher l’objet tel qu’il est stocké en base de données.
- Ensuite il faudra annoncer à EntityFramework "je vais le modifier".
- Enfin il faudra mettre en place les nouvelles valeurs.
Tout cela devra être mis dans la méthode "Edit" de votre contrôleur.
Toutes les étapes sont importantes. Si vous ne faites pas la première, EF va possiblement croire que vous êtes en train de créer une nouvelle entité.
Je vous encourage à créer la vue Edit comme nous l’avions vu plus tôt. Il faudra aussi apporter quelques légères modifications à la vue List
pour que la page d’édition soit accessible :
Trouvez les trois lignes suivantes :
Et mettez-y l’ID de l’article :
Modifier l’entité
Pour trouver l’entité, vous pouvez utiliser la méthode Where
comme vue précédemment. Le problème de cette méthode, c’est qu’elle retourne une collection, même si cette dernière ne contient qu’un élément.
Il vous faudra dès lors utiliser la méthode FirstOrDefault
pour aller chercher l’entité.
Une alternative sera d’utiliser la méthode Find
, c’est ce que nous ferons dans cette partie.
Une fois l’entité trouvée, il faut annoncer au contexte qu’elle est modifiée. C’est là que commence la partie "complexe" puisque pour s’assurer que la modification soit efficace et effective dans tous les cas, nous allons utiliser une capacité avancée de EntityFramework.
Le contexte de données contient une méthode Entry<TEntity>()
qui permet de personnaliser la manière dont les entités sont gérées par Entity Framework.
Cette méthode retourne un objet de type DbEntityEntry
qui a la possibilité de forcer l’état d’une entité, utiliser des fonctionnalité de différentiel (c’est-à-dire trouver la différence entre l’ancienne et la nouvelle version)…
Pour vous aider, voici le code :
J’ai encapsulé la gestion de l’image dans une méthode pour que ça soit plus facile à utiliser.
La suppression de l’entité
La suppression se veut, bien heureusement plus simple.
Si l’utilisation de Entry
est toujours possible, avec un simple changement d’état à Deleted
, vous pouvez utiliser la méthode bdd.Articles.Remove
qui prend en paramètre votre entité.
N’oubliez pas de sauvegarder les changements et c’est bon.
Par contre, je tenais à préciser une chose : la vue par défaut vous propose d’utiliser un lien et donc une requête GET. Il vaut mieux changer ça !
Utilisez une requête POST
et assurez-vous d’être protégés contre la faille CSRF.
Je vous laisse le faire en exercice.
Le patron de conception Repository
Le patron de conception Repository
a pour but de remplacer la ligne ApplicationDbContext bdd = ApplicationDbContext.Create();
par une ligne plus "générique".
J’entends par là que nous allons créer une abstraction supplémentaire permettant de remplacer à la volée un ApplicationDbContext
par autre chose ne dépendant pas d’entity Framework.
Tu nous apprends Entity Framework, mais tu veux le remplacer par autre chose, je ne comprends pas pourquoi !
En fait il existe des projets qui doivent pouvoir s’adapter à n’importe quel ORM, c’est rare, mais ça arrive.
Mais surtout, il faut bien comprendre qu’un site web, ça ne se développe pas n’importe comment.
En effet, si on simplifie beaucoup les choses, quand vous développez un site web, module par module, vous allez réfléchir en répétant inlassablement quatre étapes :
Premièrement, nous essayons de comprendre de quoi on a besoin. Jusqu’à maintenant, nous avons fait le travail pour vous. Par exemple, comme nous voulons créer un blog, nous vous avons dit "il faudra ajouter, modifier, supprimer des articles".
Le besoin est là, maintenant on va "concevoir". Là encore, nous vous avons bien dirigé jusqu’à présent. Par exemple, nous avons dit "un article est composé d’un texte, un titre, un auteur et éventuellement une icône".
Et là, vous avez commencé à coder. Et qu’avez-vous fait, pour voir que "ça marche" ? Eh bien vous avez testé.
N’avez-vous pas remarqué que c’est long et répétitif ?
Nous verrons plus tard dans le cours qu’il existe des outils qui testent automatiquement votre site. Mais comme ces tests ont pour but de vérifier que votre site fonctionne aussi bien quand l’utilisateur donne quelque chose de normal que des choses totalement hallucinantes, vous ne pouvez pas tester sur une base de données normale. Non seulement cela aurait pour effet de la polluer si les tests échouent, mais en plus cela ralentirait vos tests.
Alors on va passer par des astuces, que nous utiliseront plus tard mais qui elles n’utilisent pas EntityFramework.
C’est pourquoi nous allons vouloir limiter les liens entre les contrôleurs et EntityFramework. Nous allons créer un Repository1
En fait le but sera de créer une interface qui permettra de gérer les entités.
Par exemple, une telle interface peut ressembler à ça pour nos articles :
Et comme on veut pouvoir utiliser EntityFramework quand même, on crée une classe qui implémente cette interface :
Dans la suite du cours, nous n’utiliserons pas ce pattern afin de réduire la quantité de code que vous devrez comprendre. Néanmoins, sachez que dès que vous devrez tester une application qui utilise une base de données, il faudra passer par là !