Nouveautés d'EF Core 5.0
La liste suivante présente les nouvelles fonctionnalités majeures d'EF Core 5.0. Pour obtenir la liste complète des problèmes dans la version, reportez-vous à notre outil de suivi des problèmes.
En tant que version majeure, EF Core 5.0 contient également plusieurs changements cassants, qui sont des améliorations d'API ou des changements de comportement pouvant avoir un impact négatif sur les applications existantes.
Plusieurs-à-plusieurs
EF Core 5.0 prend en charge les relations plusieurs-à-plusieurs sans mapper explicitement la table de jointure.
Par exemple, tenez compte de ces types d'entités :
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public int Id { get; set; }
public string Text { get; set; }
public ICollection<Post> Posts { get; set; }
}
EF Core 5.0 reconnaît cela comme une relation plusieurs-à-plusieurs par convention et crée automatiquement une table de jointure PostTag
dans la base de données. Les données peuvent être interrogées et mises à jour sans référence explicite à la table de jointure, ce qui simplifie considérablement le code. La table de jointure peut toujours être personnalisée et interrogée explicitement si nécessaire.
Pour plus d'informations, reportez-vous à la documentation complète sur plusieurs-à-plusieurs.
Fractionner des requêtes
À compter d'EF Core 3.0, EF Core génère toujours une requête SQL unique pour chaque requête LINQ. Cela garantit la cohérence des données retournées dans les contraintes du mode transactionnel en cours d'utilisation. Toutefois, lorsque la requête utilise Include
ou une projection pour ramener plusieurs collections associées, cela peut être source d'extrême lenteur.
EF Core 5.0 permet désormais à une requête LINQ unique, y compris les collections associées, d'être divisées en plusieurs requêtes SQL. Cette division peut améliorer considérablement les performances, mais peut entraîner une incohérence dans les résultats retournés si les données changent entre les deux requêtes. Les transactions sérialisables ou d'instantanés peuvent être utilisées pour atténuer l'incohérence et obtenir une cohérence avec les requêtes fractionnées. De telles transactions peuvent induire d'autres coûts de performances et une différence comportementale.
Par exemple, prenez le cas d'une requête qui extrait deux niveaux de collections associées à l'aide Include
de :
var artists = context.Artists
.Include(e => e.Albums)
.ToList();
Par défaut, EF Core génère le code SQL suivant lors de l'utilisation du fournisseur SQLite :
SELECT a."Id", a."Name", a0."Id", a0."ArtistId", a0."Title"
FROM "Artists" AS a
LEFT JOIN "Album" AS a0 ON a."Id" = a0."ArtistId"
ORDER BY a."Id", a0."Id"
Avec les requêtes fractionnées, le code SQL suivant est généré à la place :
SELECT a."Id", a."Name"
FROM "Artists" AS a
ORDER BY a."Id"
SELECT a0."Id", a0."ArtistId", a0."Title", a."Id"
FROM "Artists" AS a
INNER JOIN "Album" AS a0 ON a."Id" = a0."ArtistId"
ORDER BY a."Id"
Les requêtes fractionnées peuvent être activées en plaçant le nouvel opérateur AsSplitQuery
n'importe où dans votre requête LINQ, ou globalement dans le OnConfiguring
de votre modèle. Pour plus d'informations, reportez-vous à la documentation complète sur les requêtes fractionnées.
Journalisation simple et diagnostics améliorés
EF Core 5.0 introduit un moyen simple de configurer la journalisation via la nouvelle méthode LogTo
. Les éléments suivants entraînent l'écriture des messages de journalisation dans la console, y compris toutes les données SQL générées par EF Core :
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
En outre, il est désormais possible d'appeler ToQueryString
sur n'importe quelle requête LINQ, en récupérant le code SQL que la requête exécuterait :
Console.WriteLine(
ctx.Artists
.Where(a => a.Name == "Pink Floyd")
.ToQueryString());
Enfin, différents types d'EF Core ont été équipés d'une propriété améliorée DebugView
qui fournit une vue détaillée des intérieurs. Par exemple, vous pouvez consulter ChangeTracker.DebugView pour voir exactement quelles entités sont suivies dans un moment donné.
Pour plus d'informations, reportez-vous à la documentation sur la journalisation et l'interception.
Inclure les entités filtrées
La méthode Include
prend désormais en charge le filtrage des entités incluses :
var blogs = context.Blogs
.Include(e => e.Posts.Where(p => p.Title.Contains("Cheese")))
.ToList();
Cette requête retourne des blogs avec chaque publication associée, mais uniquement lorsque le titre de la publication contient « Cheese. »
Pour plus d'informations, reportez-vous à la documentation complète sur les entités filtrées.
Mappage de table par type (TPT)
Par défaut, EF Core mappe une hiérarchie d'héritage de types .NET à une table de base de données unique. Il s'agit du mappage TPH (table par hiérarchie). EF Core 5.0 permet également de mapper chaque type .NET dans une hiérarchie d'héritage à une autre table de base de données appelée mappage TPT (table par type).
Prenez comme exemple ce modèle avec une hiérarchie mappée :
public class Animal
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Cat : Animal
{
public string EducationLevel { get; set; }
}
public class Dog : Animal
{
public string FavoriteToy { get; set; }
}
Avec TPT, une table de base de données est créée pour chaque type de la hiérarchie :
CREATE TABLE [Animals] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
CONSTRAINT [PK_Animals] PRIMARY KEY ([Id])
);
CREATE TABLE [Cats] (
[Id] int NOT NULL,
[EducationLevel] nvarchar(max) NULL,
CONSTRAINT [PK_Cats] PRIMARY KEY ([Id]),
CONSTRAINT [FK_Cats_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE NO ACTION,
);
CREATE TABLE [Dogs] (
[Id] int NOT NULL,
[FavoriteToy] nvarchar(max) NULL,
CONSTRAINT [PK_Dogs] PRIMARY KEY ([Id]),
CONSTRAINT [FK_Dogs_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE NO ACTION,
);
Pour plus d'informations, reportez-vous à la documentation complète sur TPT.
Mappage d'entité flexible
Les types d'entités sont généralement mappés à des tables ou des vues de sorte qu'EF Core récupère le contenu de la table ou de la vue lors de l'interrogation de ce type. EF Core 5.0 ajoute des options de mappage supplémentaires, où une entité peut être mappée à une requête SQL (appelée « requête de définition ») ou à une fonction table (TVF) :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>().ToSqlQuery(
@"SELECT Id, Name, Category, BlogId FROM posts
UNION ALL
SELECT Id, Name, ""Legacy"", BlogId from legacy_posts");
modelBuilder.Entity<Blog>().ToFunction("BlogsReturningFunction");
}
Les fonctions table peuvent également être mappées à une méthode .NET plutôt qu'à un DbSet, ce qui permet la transmission des paramètres. Le mappage peut être configuré avec HasDbFunction.
Enfin, il est désormais possible de mapper une entité à une vue lors de l'interrogation (ou d'une fonction ou d'une requête de définition), mais à une table lors de la mise à jour :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Blog>()
.ToTable("Blogs")
.ToView("BlogsView");
}
Types d'entités de type partagé et sacs de propriétés
EF Core 5.0 permet au même type CLR d'être mappé à plusieurs types d'entités différents. Ces types sont appelés types d'entités de type partagé. Bien que tout type CLR puisse être utilisé avec cette fonctionnalité, .NET Dictionary
offre un cas d'utilisation particulièrement attrayant que nous appelons « sacs de propriétés. »
public class ProductsContext : DbContext
{
public DbSet<Dictionary<string, object>> Products => Set<Dictionary<string, object>>("Product");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Product", b =>
{
b.IndexerProperty<int>("Id");
b.IndexerProperty<string>("Name").IsRequired();
b.IndexerProperty<decimal>("Price");
});
}
}
Ces entités peuvent ensuite être interrogées et mises à jour comme les types d'entités normaux avec leur propre type CLR dédié. Vous trouverez plus d'informations dans la documentation sur les sacs de propriétés.
Dépendances requises 1:1
Dans EF Core 3.1, la fin dépendante d'une relation de un-à-un était toujours considérée comme facultative. Cela était le plus évident lors de l'utilisation d'entités détenues, car toutes les colonnes de l'entité détenue ont été créées comme pouvant accepter la valeur Null dans la base de données, même si elles ont été configurées comme requis dans le modèle.
Dans EF Core 5.0, une navigation vers une entité détenue peut être configurée comme dépendante requise. Par exemple :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>(b =>
{
b.OwnsOne(e => e.HomeAddress,
b =>
{
b.Property(e => e.City).IsRequired();
b.Property(e => e.Postcode).IsRequired();
});
b.Navigation(e => e.HomeAddress).IsRequired();
});
}
DbContextFactory
EF Core 5.0 introduit AddDbContextFactory
et AddPooledDbContextFactory
pour inscrire une fabrique pour créer des instances DbContext dans le conteneur d'injection de dépendances de l'application (D.I.). Cela peut être utile lorsque le code de l'application doit créer et supprimer manuellement des instances de contexte.
services.AddDbContextFactory<SomeDbContext>(b =>
b.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"));
À ce stade, les services d'application comme les contrôleurs ASP.NET Core peuvent ensuite être injectés de IDbContextFactory<TContext>
, et l'utiliser pour instancier des instances de contexte :
public class MyController : Controller
{
private readonly IDbContextFactory<SomeDbContext> _contextFactory;
public MyController(IDbContextFactory<SomeDbContext> contextFactory)
=> _contextFactory = contextFactory;
public void DoSomeThing()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
}
Pour plus d'informations, reportez-vous à la documentation complète sur DbContextFactory.
Régénérations de table SQLite
Par rapport à d'autres bases de données, SQLite est relativement limité dans ses fonctionnalités de manipulation de schéma. Par exemple, la suppression d'une colonne d'une table existante n'est pas prise en charge. EF Core 5.0 fonctionne autour de ces limitations en créant automatiquement une nouvelle table, en copiant les données de l'ancienne table, en supprimant l'ancienne table et en renommant la nouvelle table. Cette opération « régénère » la table et permet l'application en toute sécurité des opérations de migration précédemment non prises en charge.
Pour plus d'informations sur les opérations de migration qui sont désormais prises en charge via les reconstructions de tables, reportez-vous à cette page de documentation.
Classements de base de données
EF Core 5.0 introduit la prise en charge de la spécification de classements de texte au niveau de la base de données, de la colonne ou de la requête. Cela permet la configuration de la sensibilité de la casse et d'autres aspects textuels d'une manière flexible et ne compromet pas les performances des requêtes.
Par exemple, les éléments suivants configurent la colonne Name
pour qu'elle respecte la casse sur SQL Server, et tous les index créés sur la colonne fonctionnent en conséquence :
modelBuilder
.Entity<User>()
.Property(e => e.Name)
.UseCollation("SQL_Latin1_General_CP1_CS_AS");
Pour plus d'informations, reportez-vous à la documentation complète sur les classements et la sensibilité à la casse.
Compteurs d’événements
EF Core 5.0 expose des compteurs d'événements qui peuvent être utilisés pour suivre les performances de votre application et repérer diverses anomalies. Attachez simplement à un processus exécutant EF avec l'outil dotnet-counters :
> dotnet counters monitor Microsoft.EntityFrameworkCore -p 49496
[Microsoft.EntityFrameworkCore]
Active DbContexts 1
Execution Strategy Operation Failures (Count / 1 sec) 0
Execution Strategy Operation Failures (Total) 0
Optimistic Concurrency Failures (Count / 1 sec) 0
Optimistic Concurrency Failures (Total) 0
Queries (Count / 1 sec) 1,755
Queries (Total) 98,402
Query Cache Hit Rate (%) 100
SaveChanges (Count / 1 sec) 0
SaveChanges (Total) 1
Pour plus d'informations, reportez-vous à la documentation complète sur les compteurs d'événements.
Autres fonctionnalités
Génération de modèles
- Les API de génération de modèles ont été introduites pour faciliter la configuration des comparateurs de valeurs.
- Les colonnes calculées peuvent désormais être configurées comme stockées ou virtuelles.
- La précision et l'échelle peuvent désormais être configurées via l'API Fluent.
- De nouvelles API de génération de modèles ont été introduites pour les propriétés de navigation.
- De nouvelles API de génération de modèles ont été introduites pour les champs, et sont similaires aux propriétés.
- Les types .NET PhysicalAddress et IPAddress peuvent désormais être mappés aux colonnes de chaîne de base de données.
- Un champ de stockage peut désormais être configuré via le nouvel
[BackingField]
attribut. - Les champs de stockage pouvant accepter la valeur Null sont désormais autorisés, ce qui offre une meilleure prise en charge des valeurs par défaut générées par le magasin où la valeur CLR n'est pas une bonne valeur sentinelle (notable
bool
). - Un nouvel attribut
[Index]
peut être utilisé sur un type d'entité pour spécifier un index, au lieu d'utiliser l'API Fluent. - Un nouvel attribut
[Keyless]
peut être utilisé pour configurer un type d'entité comme n'ayant aucune clé. - Par défaut, EF Core considère désormais les discriminateurs comme étant complets, ce qui signifie qu'il s'attend à ne jamais voir les valeurs de discrimination non configurées par l'application dans le modèle. Cela permet d'améliorer les performances et peut être désactivé si votre colonne de discrimination peut contenir des valeurs inconnues.
Requête
- Les exceptions d'échec de traduction de requête contiennent désormais des raisons plus explicites concernant les raisons de l'échec, afin d'identifier le problème.
- Les requêtes sans suivi peuvent désormais effectuer une résolution d'identité, ce qui évite de retourner plusieurs instances d'entité pour le même objet de base de données.
- Ajout de la prise en charge de GroupBy avec des agrégats conditionnels (par exemple
GroupBy(o => o.OrderDate).Select(g => g.Count(i => i.OrderDate != null))
). - Ajout de la prise en charge de la traduction de l'opérateur Distinct sur les éléments de groupe avant l'agrégat.
- Traduction de
Reverse
. - Amélioration de la traduction autour de
DateTime
pour SQL Server (par exempleDateDiffWeek
, ,DateFromParts
). - Traduction de nouvelles méthodes sur des tableaux d'octets (par exemple
Contains
,Length
,SequenceEqual
). - Traduction de certains opérateurs au niveau du bit supplémentaires, tels que le complément de deux.
- Traduction de
FirstOrDefault
sur les chaînes. - Traduction améliorée des requêtes autour de la sémantique Null, ce qui entraîne des requêtes plus ciblées et plus efficaces.
- Les fonctions mappées par l'utilisateur peuvent désormais être annotées pour contrôler la propagation nulle, ce qui entraîne à nouveau des requêtes plus ciblées et plus efficaces.
- SQL contenant des blocs CASE est désormais beaucoup plus concis.
- La fonction SQL Server
DATALENGTH
peut désormais être appelée dans les requêtes via la nouvelle méthodeEF.Functions.DataLength
. EnableDetailedErrors
ajoute des détails supplémentaires aux exceptions.
Enregistrement
- interception et événements SaveChanges.
- Les API ont été introduites pour contrôler les points d'enregistrement des transactions. En outre, EF Core crée automatiquement un point d'enregistrement lorsque
SaveChanges
est appelé et qu'une transaction est déjà en cours et la restaure en cas de défaillance. - Un ID de transaction peut être défini explicitement par l'application, ce qui facilite la corrélation des événements de transaction dans la journalisation et ailleurs.
- La taille de lot maximale par défaut pour SQL Server a été remplacée par 42 en fonction d'une analyse des performances de traitement par lots.
Migrations et génération de modèles automatique
- Les tables peuvent désormais être exclues des migrations.
- Une nouvelle commande
dotnet ef migrations list
indique désormais quelles migrations n'ont pas encore été appliquées à la base de données (Get-Migration
effectue la même chose dans la Package Management Console). - Les scripts de migration contiennent désormais des instructions transactionnelles, le cas échéant, pour améliorer la gestion des cas où l'application de migration échoue.
- Les colonnes des classes racines démappées sont désormais classées après d'autres colonnes pour les types d'entités mappés. Notez que cela affecte uniquement les tables nouvellement créées. L'ordre des colonnes pour les tables existantes reste inchangé.
- La génération de migration peut désormais être consciente si la migration générée est idempotente et si la sortie sera exécutée immédiatement ou générée en tant que script.
- De nouveaux paramètres de ligne de commande ont été ajoutés pour spécifier des espaces de noms dans Migrations et la génération de modèles automatique.
- La commande dotnet ef database update accepte désormais un nouveau paramètre
--connection
pour la spécification de la chaîne de connexion. - La génération automatique de bases de données existantes singularise désormais les noms de tables, de sorte que les tables nommées
People
etAddresses
seront générées automatiquement en types d'entités appelésPerson
etAddress
. Les noms de base de données d'origine peuvent toujours être conservés. - La nouvelle option
--no-onconfiguring
peut indiquer à EF Core d'exclureOnConfiguring
lors de la génération de modèles automatique d'un modèle.
Azure Cosmos DB
- Les paramètres de connexion Azure Cosmos DB ont été développés.
- L'accès concurrentiel optimiste est désormais pris en charge sur Azure Cosmos DB via l'utilisation d'ETags.
- La nouvelle méthode
WithPartitionKey
permet l'inclusion de la clé de partition Azure Cosmos DB à la fois dans le modèle et dans les requêtes. - Les méthodes de chaîne
Contains
etStartsWith
etEndsWith
sont désormais traduites pour Azure Cosmos DB. - L'opérateur C#
is
est désormais traduit sur Azure Cosmos DB.
Sqlite
- Les colonnes calculées sont désormais prises en charge.
- La récupération de données binaires et de chaînes avec GetBytes, GetChars et GetTextReader est désormais plus efficace en utilisant SqliteBlob et des flux.
- L'initialisation de SqliteConnection est désormais différée.
Autres
- Les proxys de suivi des modifications peuvent être générés. Ils implémentent automatiquement INotifyPropertyChanging et INotifyPropertyChanged. Cela offre une autre approche du suivi des modifications qui n'analyse pas les modifications lorsque
SaveChanges
est appelé. - DbConnection ou chaîne de connexion peut désormais être modifiée sur un DbContext déjà initialisé.
- La nouvelle méthode ChangeTracker.Clear efface DbContext de toutes les entités suivies. Cela ne doit généralement pas être nécessaire lors de l'utilisation de la meilleure pratique de création d'une instance de contexte de courte durée pour chaque unité de travail. Toutefois, s'il est nécessaire de réinitialiser l'état d'une instance DbContext, l'utilisation de la nouvelle méthode
Clear()
est plus efficace et robuste que le détachement en masse de toutes les entités. - Les outils en ligne de commande EF Core configurent désormais automatiquement les variables d’environnement
ASPNETCORE_ENVIRONMENT
etDOTNET_ENVIRONMENT
sur « Développement ». Cela apporte l'expérience lors de l'utilisation de l'hôte générique conformément à l'expérience de ASP.NET Core pendant le développement. - Les arguments de ligne de commande personnalisés peuvent être acheminés vers IDesignTimeDbContextFactory<TContext>, ce qui permet aux applications de contrôler la façon dont le contexte est créé et initialisé.
- Le facteur de remplissage d'index peut maintenant être configuré sur SQL Server.
- La nouvelle propriété IsRelational peut être utilisée pour distinguer l'utilisation d'un fournisseur relationnel et d'un fournisseur non relationnel (par exemple, en mémoire).