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 Includede :

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 exemple DateDiffWeek, , 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éthode EF.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 et Addresses seront générées automatiquement en types d'entités appelés Person et Address. Les noms de base de données d'origine peuvent toujours être conservés.
  • La nouvelle option --no-onconfiguring peut indiquer à EF Core d'exclure OnConfiguring lors de la génération de modèles automatique d'un modèle.

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_ENVIRONMENTetDOTNET_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).