Quoi de neuf dans Entity Framework Core ?

Entity Framework s’est fait une belle place dans le monde merveilleux (et polémique) des ORM dans le monde .NET. On n’entend quasiment plus parler du vénérable NHibernate. Paix à son âme, il ne me manquera pas outre-mesure.
Dans les projets modernes, les concurrents s’appellent plutôt Dapper ou Simple.Data, bien plus légers entre contrepartie d’une couverture fonctionnelle moins étendue. Entity Framework (EF pour les intimes) en était jusqu’à tout récemment à sa version 6.1.3, et on le retrouve dans énormément de projets .NET. Surtout quand la base de données attaquée est SQL Server. Mais en parallèle, l’équipe travaillait depuis plusieurs années à une réécriture totale. D’ailleurs, le projet s’est d’abord appelé Entity Framework Everywhere, puis Entity Framework 7 (qui était packagé avec MVC 6 dans ASP.NET 5, vous suivez ?). Résultat des courses, la nouvelle version s’appelle maintenant Entity Framework Core. Et toute sa doc se trouve à cette adresse.

EF, comme mentionné juste au-dessus, a été revu du sol au plafond. Et si son utilisation ne devrait pas décontenancer les habitués à EF6 (rassurez-vous, DbContext et DbSet sont toujours là), il y a tout de même un nombre considérable de nouveautés. Attention, néanmoins. Si EF Core apporte son lot de nouveautés, EF6 est toujours bien plus mature et bon nombre de fonctionnalités d’EF6 n’ont pas encore été portées vers EF Core. Pour faire votre choix, vous pouvez vous référer à cette page.
Les principales fonctionnalités non-encore portées dans EF Core 1.0 sont à mon sens :

  • pas d’EDMX, donc pas de visualisation graphique du modèle
  • pas de gestion des types spatiaux geography et geometry
  • pas de lazy et explicit loading
  • pas de mapping avec les procédures stockées

Bonne nouvelle, ça arrive. Ainsi que plein d’autres choses, dont certaines sur lesquelles nous reviendrons. Et la roadmap est là pour en attester.

Donc voilà pour le préambule. Jetons maintenant un oeil averti aux nouveautés d’EF Core.

Légèreté et modularité

EF6 est un lointain descendant de Linq to SQL, et est devenu avec le temps un monolithe. Un des buts de la réécriture de EF sous la forme EF Core est de casser ce monolithe en briques extensibles et remplaçables. Tout le couplage est lâche et se fait via un moteur d’injection de dépendances. Si vous n’allez intéragir qu’avec une base de données Azure Table Storage (quand ce sera prêt…), alors vous n’aurez à subir aucun composant lié au monde de la donnée relationnelle. Un bon indicateur est le nombre de projets contenus dans la solution EntityFramework.sln

Dorénavant, le coeur d’EF Core (et désolé pour la redondance) ne contient plus que :

  • DbContext
  • DbSet
  • Database
  • ChangeTracker

Ce sont les seules fonctionnalités communes à tous les providers qui existeront dans EF Core.

Il existe en option des services transverses à l’ensemble des providers : gestion d’état, gestion de cache, permettant d’améliorer les performances et les capacités d’EF Core. Mais ces services ne sont, je le répète, qu’optionnels. Tout est en opt-in, en injectant la configuration souhaitée dans une surcharge de DbContext.OnConfiguring().

Performances

Un des principaux reproches faits aux ORM en général et à EF en particulier concerne les perfs. Ca a donc été un axe de travail fondamental dans le développement de EF Core. Dans cette vidéo, Rowan Miller de l’équipe EF fait état, démo à l’appui, d’un gain de 80% en moyenne sur des cas avec et sans cache client géré par EF. Et là où c’est réellement intéressant, c’est que les implémentations naïves de requêtes avec EF Core sont encore 50% plus rapides que les implémentations optimisées pour EF 6 (insertions en masse, désactivation de tracking…).

Autre ajout intéressant : l’exécution de DbContext.SaveChanges() génère une et une seule requête en écriture, générée dynamiquement à partir des entités trackées par l’instance courante de DbContext. Et au besoin, il est possible de customiser le nombre maximal d’actions d’écriture au travers de la propriété MaxBatchSize du DbContextOptionsBuilder passé en paramètre de votre surcharge de DbContext.OnConfiguring().

Sur un exemple de 1000 insertions dans une base de données distante sur Azure, on parle d’un gain de plus de 90%, passant de 6500ms avec EF6 à 370ms avec EF Core. Et les temps de traitement d’EF Core passent même à moins de 200ms, là où celles d’EF6 restent stables. C’est réellement impressionnant.

Troubleshooting

Un gros boulot a été fait autour de la qualité du code SQL généré depuis une requête Linq. Ces requêtes sont bien plus faciles à comprendre pour un être humain. Et la mécanique de log a été simplifiée.

Il suffit désormais d’écrire deux classes :

  • une qui hérite de ILogger, qui, surprise, loggue
  • et une qui hérite de ILoggerProvider, qui fait office de factory d’instances d’ILogger

Le logger pourra écrire dans un fichier, une base de données, la console, l’Observateur d’Evénements Windows, un bus, ElasticSearch… comme vous voulez.

Il faudra ensuite passer à l’étape de déclaration de votre ILoggerProvider. Avec .NET Core, ça se fait de façon un peu magique : il suffit d’ajouter la ligne suivante dans la méthode Configure() de votre classe Startup().

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
  loggerFactory.AddProvider(new MyLoggerProvider());
}

Pour tout autre type d’application, il faudra un peu plus de travail pour lier le logger à l’instance de DbContext :

using (var db = new MyDbContext())
{
  var serviceProvider = db.GetInfrastructure<IServiceProvider>();
  var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
  loggerFactory.AddProvider(new MyLoggerProvider());
}

Cross-platform et open source

Comme toute la plateforme .NET Core, EF Core est cross-platform et open source. On le lit et on le répète souvent, mais c’est un grand pas en avant. Si vous aviez buté jusqu’ici sur des soucis de performances, voire des comportements erratiques lors d’un Attach d’un objet complexe à un DbContext représentant un modèle complexe, vous pourrez désormais savoir très facilement ce qui se passe sous le capot. Et vous pourrez naturellement faire tourner vos applications sous Linux et MacOS.

Testabilité

Jusqu’à EF6, il fallait pas mal de boulot pour tester son code. Le plus souvent, on posait un repository par-dessus la couche d’accès aux données. On en a pas mal discuté ici. Une nouvelle fonctionnalité vraiment pratique avec EF Core est le nouveau provider in memory, permettant comme son nom l’indique de travailler en mémoire et de s’affranchir de la connexion à la base de données. Il existe bien quelques contraintes, mais pas de quoi gommer l’énorme avantage que cela procure en matière de testabilité :

  • pas de gestion des contraintes d’intégrité : rien n’empêche par exemple d’insérer deux instances ayant la même clé primaire
  • si une de vos propriétés est initialisée avec DefaultValueSql(string), cela n’aura aucun effet avec le provider in memory, puisque c’est le moteur SQL de la base de données qui s’en charge

Si ces contraintes ne sont pas éliminatoires pour vous, voici comment procéder. Pour commencer, ajoutez un nouveau constructeur à votre DbContext, permettant d’injecter des options :

public MyDbContext(DbContextOptions<BloggingContext> options)
  : base(options) 
{
}

Ensuite, dans les classes de test, vous aurez besoin d’une méthode permettant d’initialiser ces options :

private static DbContextOptions<MyDbContext> BuildInMemoryDbOptions()
{
  var serviceProvider = new ServiceCollection()
    .AddEntityFrameworkInMemoryDatabase()
    .BuildServiceProvider();          
  
  var builder = new DbContextOptionsBuilder<MyDbContext>();
  builder.UseInMemoryDatabase()
    .UseInternalServiceProvider(serviceProvider);

  return builder.Options;
}

Et enfin, dans les tests, initialisez votre DbContext avec ces options :

using (var context = new MyDbContext(BuildInMemoryDbOptions()))
{
  ...
}

NoSQL… et le monde non-relationnel en général

Alors oui, un ORM sert théoriquement à faire du mapping objet – relationnel. Mais EF Core fera un gros pas en avant vers le monde non-relationnel… mais pas tout de suite. Des providers pour Azure Table Storage, MongoDb, DocumentDb, Redis sont évoqués, mais la priorité est donnée aux bases de données relationnelles pour le moment. Le principal avantage que j’y verrais serait de bénéficier out of the box des patterns Repository et Unit of Work implémentés respectivement par DbSet et DbContext.

La ligne de commande : dotnet CLI

On aime les lignes de commande. On aime beaucoup les lignes de commande. Et la nouvelle dotnet CLI va clairement dans le bon sens. La situation jusqu’à EF6 était plutôt bancale : il y avait une ligne de commande, mais on y accédait via la console Nuget. Dans les faits, cette dernière existe toujours, à des fins de rétrocompatibilité, mais faisons comme si elle était morte. Balles neuves avec EF Core, on passe tout sur la dotnet CLI.

Il y a 3 groupes de commandes, permettant de manipuler respectivement la base de données, le DbContext et enfin les migrations.

dotnet ef database

Deux options ici : drop et update. Comme vous pouvez vous en douter, drop permet de supprimer une base de données pour un environnement donné (à donc utiliser avec parcimonie…). Update permet de jouer les migrations manquantes.

dotnet ef dbcontext

L’option list permet de lister les DbContext de votre projet. Plus intéressante, l’option scaffold permet de générer un DbContext et les DbSet associés depuis une base de données existante.

dotnet ef migrations

C’est cette commande que l’on utilisera le plus souvent. Elle permet de manipuler les migrations EF. Pour rappel, une migration représente une modification de votre modèle de données, passant d’une version v à v+1. 4 options ici : add permet d’ajouter une nouvelle migration. Un travail de rétroengineering est fait afin de détecter les modifications apportées aux DbSet et le résultat est une nouvelle classe héritant de DbMigration ajoutée au projet. L’option list permet de lister les migrations. Avec remove, vous pouvez supprimer la dernière migration. Utile si vous voulez amender une migration existante en cas d’un oubli de propriété, par exemple. Dans ce cas, remove suivi de add vous permet de retomber sur vos pieds.

Enfin, l’option script permet de générer le script SQL permettant de mettre à jour la base de données. Il est possible de spécifier la plage de migrations à générer. Et, intéressant, il existe une option –idempotant pour générer des scripts idempotants et donc rejouables à l’infini.

Cette nouvelle batterie de lignes de commande sont un vrai plus pour Entity Framework. Il sera maintenant beaucoup plus aisé de maintenir la base de données à l’aide de quelques commandes automatisées. Cela demande un peu de rigueur (mais la maintenance d’une base relationnelle en demande quoi qu’il arrive), mais on peut s’économiser quelques suées. Pour plus d’infos, c’est par ici que ça se passe.

Bonus

On peut dorénavant composer une requête Linq en mélangeant du SQL brut et des commandes Linq for Entities, et l’ensemble des opérations seront faites au niveau de la base de données. C’est une fonctionnalité réellement intéressante, là où avec EF6, il faudrait effectuer une bonne partie de ce travail en mémoire. Cela ne servira que très ponctuellement, mais si on peut confier l’ensemble des opérations de requête à la base de données, on ne va pas s’en priver.

Conclusion

Entity Framework Core est une vraie réécriture du sol au plafond. Il manque encore quelques coups de peinture dans le salon, et quelques pièces pour ceux qui voudront utiliser EF avec notamment les bases NoSQL, mais les fondations semblent bien solides. Un boulot énorme a été effectué pour dégager cette impression de boîte noire que l’on a pu connaître avec les versions précédentes. Les premières impressions donnent un sentiment de maîtrise très appréciable. Et l’ajout d’une vraie ligne de commande est un réel plus.

Tags: .net, .netcore,

3 Commentaires Laisser un commentaire

Ca fait bien de rafraichir ce sujet d’Entity Framework – et revoir la donne de performance et l’impact sur les architectures applicatives. On attend avec impatience la vue envers NoSQL (on parle poliglot ?…)
Super article, merci !

Répondre

Merci pour cet excellent article, continuez comme ça ! 🙂

Répondre
Rénald VENANT-VALERY
novembre 29, 2016 08:59

Bon article et belles perspectives vers le NoSql… Par contre, je pense qu’ils sont encore loin du compte sur certaines choses (à moins qu’ils n’aient fait le nécessaire). A quand des ValueObjects ? Quid des public setters sur chacune des propriétés des entités (le truc calamiteux par excellence) ? Quid d’un vrai moteur de mapping (chose qu’aurait pu promettre l’edmx, en y rajoutant un peu d’extensibilité…). Moi je dis : wait and see…

Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *