Changements cassants dans EF Core 6.0

Les modifications de comportement et d’API suivantes peuvent interrompre la mise à jour des applications existantes vers EF Core 6.0.

Framework cible

EF Core 6.0 cible .NET 6. Les applications ciblant les versions antérieures de .NET, .NET Core et .NET Framework devront cibler .NET 6 pour utiliser EF Core 6.0.

Résumé

Modification critique Impact
Dépendances facultatives imbriquées partageant une table et sans propriétés requises ne peuvent pas être enregistrées Élevée
La modification du propriétaire d’une entité détenue lève désormais une exception Moyenne
Azure Cosmos DB : les types d’entités connexes sont découverts comme appartenant à Moyenne
SQLite : les connexions sont mises en pool Moyenne
Les relations plusieurs-à-plusieurs sans entités de jointure mappées sont désormais générées Moyenne
Mappage nettoyé entre les valeurs DeleteBehavior et ON DELETE Faible
Base de données en mémoire valide les propriétés requises ne contiennent pas de valeurs Null Faible
Supprimer la dernière COMMANDE BY lors de la jointure pour les regroupements Faible
DbSet n’implémente plus les IAsyncEnumerable Faible
Le type d’entité de retour de TVF est également mappé à une table par défaut Faible
L’unicité du nom de la contrainte de validation est maintenant validée Faible
Ajout des interfaces de métadonnées IReadOnly et suppression des méthodes d’extension Faible
IExecutionStrategy est désormais un service singleton Faible
SQL Server : plus d’erreurs sont considérées comme temporaires Faible
Azure Cosmos DB : d’autres caractères sont échappés dans les valeurs « id » Faible
Certains services Singleton sont désormais étendus Faible*
Nouvelle API de mise en cache pour les extensions qui ajoutent ou remplacent des services Faible*
Nouvelle procédure d’initialisation de modèle de capture instantanée et au moment du design Faible
OwnedNavigationBuilder.HasIndex retourne un autre type maintenant Faible
DbFunctionBuilder.HasSchema(null) remplace [DbFunction(Schema = "schema")] Faible
Les navigations pré-initialisées sont remplacées par les valeurs des requêtes de base de données Faible
Les valeurs de chaîne enum inconnues dans la base de données ne sont pas converties en valeurs par défaut d’énumération lors de la requête Faible
DbFunctionBuilder. HasTranslation fournit désormais les arguments de fonction en tant que IReadOnlyList au lieu de IReadOnlyCollection Faible
Le mappage de table par défaut n’est pas supprimé lorsque l’entité est mappée à une fonction table Faible
dotnet-ef cible .NET 6 Faible
IModelCacheKeyFactory implémentations doivent peut-être être mises à jour pour gérer la mise en cache au moment du design Bas
NavigationBaseIncludeIgnored est maintenant considéré comme une erreur par défaut Bas

* Ces modifications sont particulièrement intéressantes pour les auteurs de fournisseurs et d’extensions de base de données.

Modifications à fort impact

Les dépendances facultatives imbriquées partageant une table et sans propriétés requises ne sont pas autorisées

problème de suivi #24558

Ancien comportement

Les modèles avec des dépendants facultatifs imbriqués partageant une table et sans propriétés requises sont autorisés, mais peuvent entraîner une perte de données lors de l’interrogation des données, puis l’enregistrement à nouveau. Par exemple, considérez le modèle suivant :

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ContactInfo ContactInfo { get; set; }
}

[Owned]
public class ContactInfo
{
    public string Phone { get; set; }
    public Address Address { get; set; }
}

[Owned]
public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

Aucune des propriétés dans ContactInfo ou Address n’est requise, et tous ces types d’entités sont mappés à la même table. Les règles de dépendances facultatives (par opposition aux dépendances requises) indiquent que si toutes les colonnes de ContactInfo sont null, aucune instance de ContactInfo ne sera créée lors de l’interrogation du propriétaire Customer. Toutefois, cela signifie également qu’aucune instance de Address ne sera créée, même si les colonnes Address ne sont pas null.

Nouveau comportement

Si vous tentez d’utiliser ce modèle, l’exception suivante est levée :

System.InvalidOperationException : le type d’entité « ContactInfo » est un dépendant facultatif à l’aide du partage de tables et contenant d’autres dépendants sans propriété non partagée requise pour identifier si l’entité existe. If all nullable properties contain a null value in database then an object instance won't be created in the query causing nested dependent's values to be lost. Ajoutez une propriété requise pour créer des instances avec des valeurs Null pour d’autres propriétés ou marquer la navigation entrante comme nécessaire pour toujours créer une instance.

Cela empêche la perte de données lors de l’interrogation et de l’enregistrement des données.

Pourquoi

L’utilisation de modèles avec des dépendants facultatifs imbriqués partageant une table et sans propriétés requises entraîne souvent une perte de données silencieuse.

Corrections

Évitez d’utiliser des dépendances facultatives partageant une table et sans propriétés requises. Il existe trois façons simples de procéder :

  1. Rendez les dépendants nécessaires. Cela signifie que l’entité dépendante aura toujours une valeur après son interrogation, même si toutes ses propriétés sont null. Par exemple :

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        [Required]
        public Address Address { get; set; }
    }
    

    Ou :

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.OwnsOne(e => e.Address);
                b.Navigation(e => e.Address).IsRequired();
            });
    
  2. Assurez-vous que la dépendance contient au moins une propriété requise.

  3. Mappez les dépendances facultatives à leur propre table, au lieu de partager une table avec le principal. Par exemple :

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.ToTable("Customers");
                b.OwnsOne(e => e.Address, b => b.ToTable("CustomerAddresses"));
            });
    

Les problèmes liés aux dépendances facultatives et exemples de ces atténuations sont inclus dans la documentation relative à nouveautés d’EF Core 6.0.

Changements à impact moyen

La modification du propriétaire d’une entité détenue lève désormais une exception

Problème de suivi #4073

Ancien comportement

Il était possible de réassigner une entité appartenant à une autre entité propriétaire.

Nouveau comportement

Cette action lèvera à présent une exception :

Propriété « {entityType} ». {property}' fait partie d’une clé et ne peut donc pas être modifié ou marqué comme modifié. Pour modifier le principal d’une entité existante avec une clé étrangère identifiante, supprimez d’abord le dépendant et appelez « SaveChanges », puis associez le dépendant au nouveau principal.

Pourquoi

Même s’il n’est pas nécessaire que les propriétés de clé existent sur un type détenu, EF crée toujours des propriétés Shadow à utiliser comme clé primaire et la clé étrangère qui pointe vers le propriétaire. Lorsque l’entité propriétaire est modifiée, elle entraîne la modification des valeurs de la clé étrangère sur l’entité détenue et, étant donné qu’elle est également utilisée comme clé primaire, l’identité de l’entité change. Cela n’est pas encore entièrement pris en charge dans EF Core et n’a été autorisé de manière conditionnelle que pour les entités détenues, ce qui entraîne parfois l’incohérence de l’état interne.

Corrections

Au lieu d’affecter la même instance détenue à un nouveau propriétaire, vous pouvez attribuer une copie et supprimer l’ancienne.

Problème de suivi #24803Nouveautés : valeur par défaut de propriété implicite

Ancien comportement

Comme dans d’autres fournisseurs, les types d’entités associés ont été découverts en tant que types normaux (non détenus).

Nouveau comportement

Les types d’entités associés seront désormais détenus par le type d’entité sur lequel ils ont été découverts. Seuls les types d’entités qui correspondent à une propriété DbSet<TEntity> sont découverts comme non détenus.

Pourquoi

Ce comportement suit le modèle commun de données de modélisation dans Azure Cosmos DB de l’incorporation de données associées dans un document unique. Azure Cosmos DB ne prend pas en charge en mode natif la jonction de différents documents. Par conséquent, la modélisation des entités associées comme non détenues a une utilité limitée.

Corrections

Pour configurer un type d’entité pour qu’il soit modelBuilder.Entity<MyEntity>();d’appel non propriétaire

SQLite : Les connexions sont mises en pool

Problème de suivi #13837Nouveautés : valeur par défaut : propriété implicite

Ancien comportement

Auparavant, les connexions dans Microsoft.Data.Sqlite n’étaient pas mises en pool.

Nouveau comportement

À partir de 6,0, les connexions sont désormais regroupées par défaut. Cela entraîne l’ouverture des fichiers de base de données par le processus, même après la fermeture de l’objet de connexion ADO.NET.

Pourquoi

Le regroupement des connexions sous-jacentes améliore considérablement les performances de l’ouverture et de la fermeture d’objets de connexion ADO.NET. Cela est particulièrement visible pour les scénarios où l’ouverture de la connexion sous-jacente est coûteuse comme dans le cas du chiffrement, ou dans les scénarios où il existe beaucoup de connexion à la base de données.

Corrections

Le regroupement de connexions peut être désactivé en ajoutant Pooling=False à une chaîne de connexion.

Certains scénarios (comme la suppression du fichier de base de données) peuvent maintenant rencontrer des erreurs indiquant que le fichier est toujours en cours d’utilisation. Vous pouvez effacer manuellement le pool de connexions avant d’effectuer des opérations du fichier à l’aide de SqliteConnection.ClearPool().

SqliteConnection.ClearPool(connection);
File.Delete(databaseFile);

Les relations plusieurs-à-plusieurs sans entités de jointure mappées sont désormais générées automatiquement

Problème de suivi #22475

Ancien comportement

Génération automatique (ingénierie inverse) d’un DbContext et des types d’entités d’une base de données existante mappées explicitement des tables de jointure pour joindre des types d’entités pour des relations plusieurs-à-plusieurs.

Nouveau comportement

Les tables de jointure simples contenant seulement deux propriétés de clé étrangère à d’autres tables ne sont plus mappées aux types d’entités explicites, mais sont plutôt mappées en tant que relation plusieurs-à-plusieurs entre les deux tables jointes.

Pourquoi

Les relations plusieurs-à-plusieurs sans types de jointure explicites ont été introduites dans EF Core 5.0 et sont un moyen plus propre et plus naturel de représenter des tables de jointure simples.

Corrections

Il existe deux atténuations. L’approche recommandée consiste à mettre à jour le code pour utiliser directement les relations plusieurs-à-plusieurs. Il est très rare que le type d’entité de jointure doit être utilisé directement lorsqu’il contient seulement deux clés étrangères pour les relations plusieurs-à-plusieurs.

Vous pouvez également rajouter l’entité de jointure explicite au modèle EF. Par exemple, en supposant une relation plusieurs-à-plusieurs entre Post et Tag, ajoutez le type de jointure et les navigations à l’aide de classes partielles :

public partial class PostTag
{
    public int PostsId { get; set; }
    public int TagsId { get; set; }

    public virtual Post Posts { get; set; }
    public virtual Tag Tags { get; set; }
}

public partial class Post
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

public partial class Tag
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

Ajoutez ensuite la configuration pour le type de jointure et les navigations à une classe partielle pour DbContext :

public partial class DailyContext
{
    partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>(entity =>
        {
            entity.HasMany(d => d.Tags)
                .WithMany(p => p.Posts)
                .UsingEntity<PostTag>(
                    l => l.HasOne<Tag>(e => e.Tags).WithMany(e => e.PostTags).HasForeignKey(e => e.TagsId),
                    r => r.HasOne<Post>(e => e.Posts).WithMany(e => e.PostTags).HasForeignKey(e => e.PostsId),
                    j =>
                    {
                        j.HasKey("PostsId", "TagsId");
                        j.ToTable("PostTag");
                    });
        });
    }
}

Enfin, supprimez la configuration générée pour la relation plusieurs-à-plusieurs du contexte généré. Cela est nécessaire, car le type d’entité de jointure généré automatiquement doit être supprimé du modèle avant que le type explicite puisse être utilisé. Ce code doit être supprimé chaque fois que le contexte est généré automatiquement, mais parce que le code ci-dessus se trouve dans des classes partielles, il est conservé.

Notez qu’avec cette configuration, l’entité de jointure peut être utilisée explicitement, comme dans les versions précédentes d’EF Core. Toutefois, la relation peut également être utilisée comme relation plusieurs-à-plusieurs. Cela signifie que la mise à jour du code comme celle-ci peut être une solution temporaire tandis que le reste du code est mis à jour pour utiliser la relation comme un plusieurs-à-plusieurs de la manière naturelle.

Modifications à faible impact

Nettoyage du mappage entre les valeurs DeleteBehavior et ON DELETE

Problème de suivi #21252

Ancien comportement

Certains mappages entre le comportement OnDelete() d’une relation et le comportement ON DELETE des clés étrangères dans la base de données étaient incohérents dans les migrations et la génération de modèles automatique.

Nouveau comportement

Le tableau suivant illustre les modifications apportées aux Migrations.

OnDelete() EN CAS DE SUPPRESSION
NoAction NO ACTION
ClientNoAction NO ACTION
Restreindre RESTRICT
Cascade CASCADE
ClientCascade RESTREINDREAUCUNE ACTION
SetNull SET NULL
ClientSetNull RESTREINDREAUCUNE ACTION

Les modifications apportées à génération de modèles automatique sont les suivantes.

EN CAS DE SUPPRESSION OnDelete()
NO ACTION ClientSetNull
RESTRICT ClientSetNullRestrict
CASCADE En cascade
SET NULL SetNull

Pourquoi

Les nouveaux mappages sont plus cohérents. Le comportement de base de données par défaut d’aucune ACTION est maintenant préféré par rapport au comportement de restriction plus restrictif et moins performant.

Corrections

Le comportement de OnDelete () par défaut des relations facultatives est ClientSetNull. Son mappage est passé de restriction à aucune ACTION. Cela peut entraîner la génération d’un grand nombre d’opérations dans la première migration ajoutée après la mise à niveau vers EF Core 6,0.

Vous pouvez choisir d’appliquer ces opérations ou de les supprimer manuellement de la migration, car elles n’ont aucun impact fonctionnel sur EF Core.

SQL Server ne prend pas en charge la restriction, donc ces clés étrangères ont déjà été créées en utilisant aucune ACTION. Les opérations de migration n’ont aucune incidence sur les SQL Server et peuvent être supprimées en toute sécurité.

Base de données en mémoire valide les propriétés requises ne contiennent pas de valeurs Null

Problème de suivi #10613

Ancien comportement

La base de données en mémoire a autorisé l’enregistrement des valeurs Null même lorsque la propriété a été configurée comme nécessaire.

Nouveau comportement

La base de données en mémoire lève une Microsoft.EntityFrameworkCore.DbUpdateException lorsque SaveChanges ou SaveChangesAsync est appelée et qu’une propriété requise est définie sur Null.

Pourquoi

Le comportement de la base de données en mémoire correspond désormais au comportement des autres bases de données.

Corrections

Le comportement précédent (c’est-à-dire ne pas vérifier les valeurs null) peut être restauré lors de la configuration du fournisseur en mémoire. Par exemple :

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseInMemoryDatabase("MyDatabase", b => b.EnableNullChecks(false));
}

Suppression de la dernière commande ORDER BY lors de la jointure pour les collections

Problème de suivi #19828

Ancien comportement

Lors de l’exécution de SQL jointures sur des regroupements (relations un-à-plusieurs), EF Core utilisé pour ajouter une clause ORDER BY pour chaque colonne clé de la table jointe. Par exemple, le chargement de tous les blogs avec les publications associées a été effectué via les SQL suivantes :

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]

Ces ordonnancements sont nécessaires pour la matérialisation correcte des entités.

Nouveau comportement

La dernière commande pour une jointure de collection est désormais omise :

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]

Une clause ORDER BY pour la colonne ID de la publication n’est plus générée.

Pourquoi

Chaque commande en impose des tâches supplémentaires côté base de données, et le dernier classement n’est pas nécessaire pour les besoins de matérialisation de EF Core. Les données montrent que la suppression de ce dernier classement peut entraîner une amélioration significative des performances dans certains scénarios.

Corrections

Si votre application s’attend à ce que les entités jointes soient retournées dans un ordre particulier, faites cela explicitement en ajoutant un opérateur LINQ OrderBy à votre requête.

DbSet n’implémente plus IAsyncEnumerable

Problème de suivi #24041

Ancien comportement

DbSet<TEntity>, qui est utilisé pour exécuter des requêtes sur DbContext, utilisées pour implémenter IAsyncEnumerable<T>.

Nouveau comportement

DbSet<TEntity> n’implémente plus directement IAsyncEnumerable<T>.

Pourquoi

DbSet<TEntity> a été initialement faite pour implémenter des IAsyncEnumerable<T> principalement afin d’autoriser l’énumération directe sur celle-ci via la construction foreach . Malheureusement, lorsqu’un projet fait également référence à System.Linq.Async afin de composer des opérateurs LINQ asynchrones côté client, cela a entraîné une erreur d’appel ambiguë entre les opérateurs définis sur IQueryable<T> et ceux définis sur IAsyncEnumerable<T>. C# 9 a ajouté extension GetEnumerator prise en charge des foreachboucles , en supprimant la raison principale d’origine de référencer IAsyncEnumerable.

La grande majorité des utilisations de DbSet continueront de fonctionner comme c’est le cas, puisqu’elles composent des opérateurs LINQ sur DbSet, l’énumèrent, etc. Les seules utilisations rompues sont celles qui tentent de caster DbSet directement vers IAsyncEnumerable.

Corrections

Si vous devez faire référence à un DbSet<TEntity> en tant que IAsyncEnumerable<T>, appelez DbSet<TEntity>.AsAsyncEnumerable pour le caster explicitement.

Le type d’entité de retour de TVF est également mappé à une table par défaut

Problème de suivi #23408

Ancien comportement

Un type d’entité n’a pas été mappé à une table par défaut lorsqu’il est utilisé comme type de retour d’une valeur TVF configurée avec HasDbFunction.

Nouveau comportement

Un type d’entité utilisé comme type de retour d’une fonction TVF conserve le mappage de table par défaut.

Pourquoi

Il n’est pas intuitif que la configuration d’une fonction TVF supprime le mappage de table par défaut pour le type d’entité de retour.

Corrections

Pour supprimer le mappage de table par défaut, appelez ToTable(EntityTypeBuilder, String):

modelBuilder.Entity<MyEntity>().ToTable((string?)null));

L’unicité du nom de la contrainte de validation est maintenant validée

Problème de suivi #25061

Ancien comportement

Les contraintes de vérification portant le même nom ont été autorisées à être déclarées et utilisées sur la même table.

Nouveau comportement

La configuration explicite de deux contraintes de vérification portant le même nom sur la même table entraîne désormais une exception. Les contraintes de vérification créées par une convention reçoivent un nom unique.

Pourquoi

La plupart des bases de données n’autorisent pas la création de deux contraintes de vérification portant le même nom sur la même table, et certaines nécessitent qu’elles soient uniques même entre les tables. Cela entraînerait la levée d’une exception lors de l’application d’une migration.

Corrections

Dans certains cas, les noms de contrainte de validation valides peuvent être différents en raison de cette modification. Pour spécifier explicitement le nom souhaité, appelez HasName:

modelBuilder.Entity<MyEntity>().HasCheckConstraint("CK_Id", "Id > 0", c => c.HasName("CK_MyEntity_Id"));

Ajout des interfaces de métadonnées IReadOnly et suppression des méthodes d’extension

Problème de suivi #19213

Ancien comportement

Il y avait trois ensembles d’interfaces de métadonnées : IModel, IMutableModel et IConventionModel ainsi que des méthodes d’extension.

Nouveau comportement

Un nouvel ensemble d’interfaces IReadOnly a été ajouté, par exemple IReadOnlyModel. Les méthodes d’extension précédemment définies pour les interfaces de métadonnées ont été converties en méthodes d’interface par défaut.

Pourquoi

Les méthodes d’interface par défaut autorisent la substitution de l’implémentation, ce qui est exploité par la nouvelle implémentation du modèle d’exécution pour offrir de meilleures performances.

Corrections

Ces modifications ne doivent pas affecter la plupart du code. Toutefois, si vous utilisiez les méthodes d’extension via la syntaxe d’appel statique, elle doit être convertie en syntaxe d’appel d’instance.

IExecutionStrategy est désormais un service singleton

Problème de suivi #21350

Nouveau comportement

IExecutionStrategy est maintenant un service singleton. Cela signifie que tout état ajouté dans les implémentations personnalisées reste entre les exécutions et que le délégué passé à ExecutionStrategy ne sera exécuté qu’une seule fois.

Pourquoi

Cette réduction des allocations sur deux chemins chauds dans EF.

Corrections

Les implémentations dérivant de ExecutionStrategy doivent effacer n’importe quel état dans OnFirstExecution().

La logique conditionnelle dans le délégué passé à ExecutionStrategy doit être déplacée vers une implémentation personnalisée de IExecutionStrategy.

SQL Server : plus d’erreurs sont considérées comme temporaires

Problème de suivi #25050

Nouveau comportement

Les erreurs répertoriées dans le problème ci-dessus sont désormais considérées comme temporaires. Lors de l’utilisation de la stratégie d’exécution par défaut (sans nouvelle tentative), ces erreurs sont à présent encapsulées dans une instance d’exception d’addition.

Pourquoi

Nous continuons à recueillir des commentaires des utilisateurs et de l’équipe SQL Server sur les erreurs qui doivent être considérées comme temporaires.

Corrections

Pour modifier l’ensemble d’erreurs considérées comme temporaires, utilisez une stratégie d’exécution personnalisée qui pourrait être dérivée de SqlServerRetryingExecutionStrategy - résilience de connexion - EF Core.

Azure Cosmos DB : d’autres caractères sont échappés dans les valeurs « id »

Problème de suivi #25100

Ancien comportement

Dans EF Core 5, seule '|' a été échappée dans id valeurs.

Nouveau comportement

Dans EF Core 6, '/', '\', '?' et '#' sont également placés dans des valeurs id.

Pourquoi

Ces caractères ne sont pas valides, comme indiqué dans Resource.Id. L’utilisation de ces derniers dans id entraîne l’échec des requêtes.

Corrections

Vous pouvez remplacer la valeur générée en la définissant avant que l’entité soit marquée comme Added :

var entry = context.Attach(entity);
entry.Property("__id").CurrentValue = "MyEntity|/\\?#";
entry.State = EntityState.Added;

Certains services Singleton sont désormais étendus

Problème de suivi #25084

Nouveau comportement

De nombreux services de requête et certains services au moment de la conception qui ont été enregistrés comme Singleton sont désormais inscrits en tant que Scoped.

Pourquoi

La durée de vie devait être modifiée pour permettre à une nouvelle fonctionnalité - DefaultTypeMapping - d’affecter les requêtes.

Les durées de vie des services au moment du design ont été ajustées pour correspondre aux durées de vie des services d’exécution afin d’éviter des erreurs lors de l’utilisation des deux.

Corrections

Utilisez TryAdd pour inscrire des services EF Core à l’aide de la durée de vie par défaut. Utilisez uniquement TryAddProviderSpecificServices pour les services qui ne sont pas ajoutés par EF.

Nouvelle API de mise en cache pour les extensions qui ajoutent ou remplacent des services

Problème de suivi #19152

Ancien comportement

Dans EF Core 5, GetServiceProviderHashCode retourné long et a été utilisé directement dans le cadre de la clé de cache pour le fournisseur de services.

Nouveau comportement

GetServiceProviderHashCode retourne maintenant int et est utilisé uniquement pour calculer le code de hachage de la clé de cache pour le fournisseur de services.

En outre, ShouldUseSameServiceProvider doit être implémenté pour indiquer si l’objet actuel représente la même configuration de service et peut donc utiliser le même fournisseur de services.

Pourquoi

L’utilisation d’un code de hachage dans le cadre de la clé de cache a entraîné des collisions occasionnelles qui étaient difficiles à diagnostiquer et à résoudre. La méthode supplémentaire garantit que le même fournisseur de services est utilisé uniquement lorsque cela est approprié.

Corrections

De nombreuses extensions n’exposent aucune option qui affecte les services inscrits et peuvent utiliser l’implémentation suivante de ShouldUseSameServiceProvider:

private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
    public ExtensionInfo(IDbContextOptionsExtension extension)
        : base(extension)
    {
    }

    ...

    public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
        => other is ExtensionInfo;
}

Dans le cas contraire, des prédicats supplémentaires doivent être ajoutés pour comparer toutes les options pertinentes.

Nouvelle procédure d’initialisation de modèle de capture instantanée et au moment du design

Problème de suivi #22031

Ancien comportement

Dans EF Core 5, les conventions spécifiques devaient être appelées avant que le modèle d’instantané ne soit prêt à être utilisé.

Nouveau comportement

IModelRuntimeInitializer a été introduit pour masquer certaines des étapes requises, et un modèle d’exécution a été introduit qui n’a pas toutes les métadonnées de migration, de sorte que le modèle au moment du design doit être utilisé pour la différence de modèle.

Pourquoi

IModelRuntimeInitializer supprime les étapes de finalisation du modèle, de sorte qu’elles peuvent maintenant être modifiées sans apporter d’autres changements cassants aux utilisateurs.

Le modèle d’exécution optimisé a été introduit pour améliorer les performances d’exécution. Il a plusieurs optimisations, dont l’une supprime les métadonnées qui ne sont pas utilisées au moment de l’exécution.

Corrections

L’extrait de code suivant montre comment vérifier si le modèle actuel est différent du modèle d’instantané :

var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;

if (snapshotModel is IMutableModel mutableModel)
{
    snapshotModel = mutableModel.FinalizeModel();
}

if (snapshotModel != null)
{
    snapshotModel = context.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
}

var hasDifferences = context.GetService<IMigrationsModelDiffer>().HasDifferences(
    snapshotModel?.GetRelationalModel(),
    context.GetService<IDesignTimeModel>().Model.GetRelationalModel());

Cet extrait de code montre comment implémenter IDesignTimeDbContextFactory<TContext> en créant un modèle en externe et en appelant UseModel:

internal class MyDesignContext : IDesignTimeDbContextFactory<MyContext>
{
    public TestContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder();
        optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DB"));

        var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder();
        CustomizeModel(modelBuilder);
        var model = modelBuilder.Model.FinalizeModel();

        var serviceContext = new MyContext(optionsBuilder.Options);
        model = serviceContext.GetService<IModelRuntimeInitializer>().Initialize(model);
        return new MyContext(optionsBuilder.Options);
    }
}

OwnedNavigationBuilder.HasIndex retourne un type différent maintenant

Problème de suivi #24005

Ancien comportement

Dans EF Core 5, HasIndex retourné IndexBuilder<TEntity>TEntity est le type de propriétaire.

Nouveau comportement

HasIndex retourne maintenant IndexBuilder<TDependentEntity>, où TDependentEntity est le type appartenant.

Pourquoi

L’objet générateur retourné n’a pas été correctement typé.

Corrections

La recompilation de votre assembly par rapport à la dernière version de EF Core suffit pour résoudre les problèmes provoqués par cette modification.

DbFunctionBuilder.HasSchema(null) remplace [DbFunction(Schema = "schema")]

Problème de suivi #24228

Ancien comportement

Dans EF Core 5, l’appel de HasSchema avec null valeur ne stockait pas la source de configuration, ce qui DbFunctionAttribute était en mesure de le remplacer.

Nouveau comportement

L’appel de HasSchema avec null valeur stocke désormais la source de configuration et empêche l’attribut de le remplacer.

Pourquoi

La configuration spécifiée avec l’API ModelBuilder ne doit pas être substituable par les annotations de données.

Corrections

Supprimez l’appel HasSchema pour permettre à l’attribut de configurer le schéma.

Les navigations pré-initialisées sont remplacées par les valeurs des requêtes de base de données

Problème de suivi #23851

Ancien comportement

Les propriétés de navigation définies sur un objet vide n’ont pas été modifiées pour les requêtes de suivi, mais remplacées pour les requêtes de non-suivi. Par exemple, tenez compte des types d’entités suivants :

public class Foo
{
    public int Id { get; set; }

    public Bar Bar { get; set; } = new(); // Don't do this.
}

public class Bar
{
    public int Id { get; set; }
}

Requête sans suivi pour Foo y compris Bar définir Foo.Bar sur l’entité interrogée à partir de la base de données. Par exemple, ce code :

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Imprimé Foo.Bar.Id = 1.

Toutefois, la même exécution de requête pour le suivi n’a pas remplacés Foo.Bar avec l’entité interrogée à partir de la base de données. Par exemple, ce code :

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Imprimé Foo.Bar.Id = 0.

Nouveau comportement

Dans EF Core 6.0, le comportement des requêtes de suivi correspond désormais à celui des requêtes sans suivi. Cela signifie que ce code :

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Et ce code :

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Imprimer Foo.Bar.Id = 1.

Pourquoi

Il existe deux raisons pour apporter cette modification :

  1. Pour vous assurer que les requêtes de suivi et de non-suivi ont un comportement cohérent.
  2. Lorsqu’une base de données est interrogée, il est raisonnable de supposer que le code de l’application souhaite obtenir les valeurs stockées dans la base de données.

Corrections

Il existe deux atténuations :

  1. N’interrogez pas les objets de la base de données qui ne doivent pas être inclus dans les résultats. Par exemple, dans les extraits de code ci-dessus, ne IncludeFoo.Bar pas si l’instance Bar ne doit pas être retournée à partir de la base de données et incluse dans les résultats.
  2. Définissez la valeur de la navigation après l’interrogation de la base de données. Par exemple, dans les extraits de code ci-dessus, appelez foo.Bar = new() après avoir exécuté la requête.

Pensez également à ne pas initialiser les instances d’entité associées aux objets par défaut. Cela implique que l’instance associée est une nouvelle entité, non enregistrée dans la base de données, sans aucune valeur de clé définie. Si, à la place, l’entité associée existe dans la base de données, les données du code sont fondamentalement à l’esprit des données stockées dans la base de données.

Les valeurs de chaîne enum inconnues dans la base de données ne sont pas converties en valeurs par défaut d’énumération lors de la requête

Problème de suivi #24084

Ancien comportement

Les propriétés d’énumération peuvent être mappées à des colonnes de chaîne dans la base de données à l’aide de HasConversion<string>() ou de EnumToStringConverter. Cela entraîne la conversion de valeurs de chaîne EF Core dans la colonne en membres correspondants du type d’énumération .NET. Toutefois, si la valeur de chaîne ne correspondait pas au membre enum, la propriété a été définie sur la valeur par défaut de l’énumération.

Nouveau comportement

EF Core 6.0 lève maintenant une InvalidOperationException avec le message « Impossible de convertir la valeur de chaîne '{value}' de la base de données en n’importe quelle valeur dans l’énumération{enumType}' mappée »

Pourquoi

La conversion de la valeur par défaut peut entraîner l’altération de la base de données si celle-ci est de nouveau enregistrée dans la base de données.

Corrections

Dans l’idéal, assurez-vous que la colonne de base de données contient uniquement des valeurs valides. Vous pouvez également implémenter une ValueConverter avec l’ancien comportement.

DbFunctionBuilder. HasTranslation fournit désormais les arguments de fonction en tant que IReadOnlyList au lieu de IReadOnlyCollection

Problème de suivi #23565

Ancien comportement

Lors de la configuration de la traduction pour une fonction définie par l’utilisateur à l’aide de HasTranslation méthode, les arguments de la fonction ont été fournis en tant que IReadOnlyCollection<SqlExpression>.

Nouveau comportement

Dans EF Core 6.0, les arguments sont désormais fournis en tant que IReadOnlyList<SqlExpression>.

Pourquoi

IReadOnlyList permet d’utiliser des indexeurs, de sorte que les arguments sont désormais plus faciles à accéder.

Corrections

Aucun. IReadOnlyList implémente IReadOnlyCollection interface. La transition doit donc être simple.

Le mappage de table par défaut n’est pas supprimé lorsque l’entité est mappée à une fonction table

Problème de suivi #23408

Ancien comportement

Lorsqu’une entité est mappée à une fonction table, son mappage par défaut à une table a été supprimé.

Nouveau comportement

Dans EF Core 6,0, l’entité est toujours mappée à une table à l’aide du mappage par défaut, même si elle est également mappée à une fonction table.

Pourquoi

Les fonctions table qui retournent des entités sont souvent utilisées comme application auxiliaire ou pour encapsuler une opération qui retourne une collection d’entités, plutôt que comme un remplacement strict de la table entière. Cette modification vise à être plus conforme à l’intention de l’utilisateur.

Corrections

Le mappage à une table peut être explicitement désactivé dans la configuration du modèle :

modelBuilder.Entity<MyEntity>().ToTable((string)null);

dotnet-ef cible .NET 6

Problème de suivi #27787

Ancien comportement

La commande dotnet-ef a ciblé .NET Core 3.1 depuis un certain temps. Cela vous a permis d’utiliser une version plus récente de l’outil sans installer de versions plus récentes du runtime .NET.

Nouveau comportement

Dans EF Core 6.0.6, l’outil dotnet-ef cible désormais .NET 6. Vous pouvez toujours utiliser l’outil sur les projets ciblant des versions antérieures de .NET et .NET Core, mais vous devez installer le runtime .NET 6 pour exécuter l’outil.

Pourquoi

Le SDK .NET 6.0.200 a mis à jour le comportement de dotnet tool install sur osx-arm64 pour créer un shim osx-x64 pour les outils ciblant .NET Core 3.1. Pour maintenir une expérience par défaut de travail pour dotnet-ef, nous devions le mettre à jour pour cibler .NET 6.

Corrections

Pour exécuter dotnet-ef sans installer le runtime .NET 6, vous pouvez installer une version antérieure de l’outil :

dotnet tool install dotnet-ef --version 3.1.*

IModelCacheKeyFactory implémentations doivent peut-être être mises à jour pour gérer la mise en cache au moment du design

Problème de suivi #25154

Ancien comportement

IModelCacheKeyFactory n’avez pas la possibilité de mettre en cache le modèle au moment du design séparément du modèle d’exécution.

Nouveau comportement

IModelCacheKeyFactory a une nouvelle surcharge qui permet au modèle au moment du design d’être mis en cache séparément du modèle d’exécution. L’implémentation de cette méthode peut entraîner une exception similaire à :

System.InvalidOperationException : « La configuration demandée n’est pas stockée dans le modèle optimisé en lecture, utilisez 'DbContext.GetService<IDesignTimeModel>().Model'.'

Pourquoi

L’implémentation de modèles compilés nécessite la séparation des modèles au moment de la conception (utilisé lors de la génération du modèle) et du runtime (utilisé lors de l’exécution de requêtes, etc.). Si le code d’exécution a besoin d’accéder aux informations au moment du design, le modèle au moment du design doit être mis en cache.

Corrections

Implémentez la nouvelle surcharge. Par exemple :

public object Create(DbContext context, bool designTime)
    => context is DynamicContext dynamicContext
        ? (context.GetType(), dynamicContext.UseIntProperty, designTime)
        : (object)context.GetType();

La navigation "{navigation}" est ignorée de "include" dans la requête, car elle est remplie automatiquement par le correctif. Si d’autres navigations sont spécifiées dans "include" par la suite, elles seront ignorées. Le retour dans l’arborescence include n’est pas autorisé.

Suivi de problème #4315

Ancien comportement

L’événement CoreEventId.NavigationBaseIncludeIgnored est enregistré en tant qu’avertissement par défaut.

Nouveau comportement

L’événement CoreEventId.NavigationBaseIncludeIgnored est enregistré en tant qu’erreur par défaut et entraîne l’envoi d’une exception.

Pourquoi

Étant donné que ces modèles de requête ne sont pas autorisés, EF Core envoie une erreur indiquant que les requêtes doivent être mises à jour.

Corrections

L’ancien comportement peut être restauré en configurant l’événement en tant qu’avertissement. Par exemple :

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Warn(CoreEventId.NavigationBaseIncludeIgnored));