Partager via


Changements cassants dans EF Core 9 (EF9)

Cette page documente les changements de comportement et d’API qui peuvent casser les applications existantes qui mettent à jour EF Core 8 vers EF Core 9. Veillez à passer en revue les changements cassants antérieurs si vous effectuez une mise à jour à partir d’une version antérieure d’EF Core :

Framework cible

EF Core 9 cible .NET 8. Cela signifie que les applications existantes qui ciblent .NET 8 peuvent continuer à le faire. Les applications ciblant les versions antérieures de .NET, .NET Core et .NET Framework devront cibler .NET 8 ou .NET 9 pour utiliser EF Core 9.

Résumé

Remarque

Si vous utilisez Azure Cosmos DB, veuillez consulter la section séparée ci-dessous sur les ruptures de Azure Cosmos DB.

Modification critique Impact
EF.Functions.Unhex() renvoie maintenant à byte[]? Faible
Arité des arguments de nullabilité de SqlFunctionExpression validée Faible
La méthode ToString() retourne désormais une chaîne vide pour les instances null Faible
Les dépendances du framework partagé ont été mises à jour vers la version 9.0.x Faible

Modifications à faible impact

EF.Functions.Unhex() renvoie maintenant à byte[]?

Suivi du problème n° 33864

Ancien comportement

La fonction EF.Functions.Unhex() a été précédemment annotée pour renvoyer byte[].

Nouveau comportement

À compter d’EF Core 9.0, Unhex() est maintenant annoté pour renvoyer byte[]?.

Pourquoi

Unhex() est traduit en fonction SQLite unhex, qui renvoie NULL en cas d'entrées non valides. Par conséquent, Unhex() renvoie null dans ces cas, en violation de l'annotation.

Corrections

Si vous êtes sûr que le texte transmis à Unhex() représente une chaîne hexadécimale valide, vous pouvez simplement ajouter l'opérateur null-forgiving pour affirmer que l'invocation ne renverra jamais null :

var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync();

Sinon, ajoutez des vérifications de runtime pour null sur la valeur de retour d’Unhex().

Arité des arguments de nullabilité de SqlFunctionExpression validée

Suivi du problème n° 33852

Ancien comportement

Auparavant, il était possible de créer un SqlFunctionExpression avec un nombre différent d’arguments et d’arguments de propagation de nullabilité.

Nouveau comportement

À compter d’EF Core 9.0, EF lève maintenant une exception si les nombres d’arguments et d’arguments de propagation de nullabilité ne correspondent pas.

Pourquoi

Le fait de ne pas avoir de nombres correspondants d’arguments et d’arguments de propagation nullabilité peut entraîner un comportement inattendu.

Corrections

Vérifiez que le argumentsPropagateNullability a le même nombre d’éléments que le arguments. En cas de doute, utilisez false pour l’argument de nullabilité.

La méthode ToString() retourne désormais une chaîne vide pour les instances null

Suivi du problème n° 33941

Ancien comportement

Auparavant, EF retournait des résultats incohérents pour la méthode ToString() lorsque la valeur de l’argument était null. Par exemple, ToString() sur une propriété bool? avec une valeur null retournait null, mais pour les expressions non-propriétés bool? dont la valeur était null, elle retournait True. Le comportement était également incohérent pour d’autres types de données. Par exemple, ToString() sur une énumération null retournait une chaîne vide.

Nouveau comportement

À partir d’EF Core 9.0, la méthode ToString() retourne désormais systématiquement une chaîne vide dans tous les cas où la valeur de l’argument est null.

Pourquoi

L’ancien comportement était incohérent entre différents types de données et situations, et ne correspondait pas au comportement C#.

Corrections

Pour revenir à l’ancien comportement, réécrivez la requête en conséquence :

var newBehavior = context.Entity.Select(x => x.NullableBool.ToString());
var oldBehavior = context.Entity.Select(x => x.NullableBool == null ? null : x.NullableBool.ToString());

Les dépendances du framework partagé ont été mises à jour vers la version 9.0.x

Ancien comportement

Les applications utilisant le SDK Microsoft.NET.Sdk.Web et ciblant net8.0 résolvaient des packages comme System.Text.Json, Microsoft.Extensions.Caching.Memory, Microsoft.Extensions.Configuration.Abstractions, Microsoft.Extensions.Logging et Microsoft.Extensions.DependencyModel à partir du framework partagé, donc ces assemblages n’étaient généralement pas déployés avec l’application.

Nouveau comportement

Bien qu’EF Core 9.0 prenne toujours en charge net8.0, il fait désormais référence aux versions 9.0.x de System.Text.Json, Microsoft.Extensions.Caching.Memory, Microsoft.Extensions.Configuration.Abstractions, Microsoft.Extensions.Logging et Microsoft.Extensions.DependencyModel. Les applications ciblant net8.0 ne pourront pas tirer parti du framework partagé pour éviter de déployer ces assemblages.

Pourquoi

Les versions de dépendances correspondantes contiennent les dernières corrections de sécurité et leur utilisation simplifie le modèle de maintenance pour EF Core.

Corrections

Modifiez votre application pour cibler net9.0 afin de retrouver le comportement précédent.

Modifications majeures d’Azure Cosmos DB

Un travail considérable a été effectué pour améliorer le fournisseur Azure Cosmos DB dans la version 9.0. Les changements incluent un certain nombre de ruptures à fort impact ; si vous mettez à jour une application existante, veuillez lire attentivement ce qui suit.

Modification critique Impact
La propriété Discriminator est désormais nommée $type au lieu de Discriminator Élevé
La propriété id ne contient plus le discriminant par défaut Élevé
Les E/S synchrones via le fournisseur Azure Cosmos DB n’est plus pris en charge. Moyenne
Les requêtes SQL doivent désormais projeter directement des valeurs JSON Moyenne
Les résultats non définis sont désormais automatiquement filtrés des résultats de la requête. Moyenne
Les requêtes mal traduites ne sont plus traduites Moyenne
HasIndex est désormais lancé au lieu d'être ignoré Faible
IncludeRootDiscriminatorInJsonId a été renommé en HasRootDiscriminatorInJsonIdaprès 9.0.0-rc.2 Faible

Modifications à fort impact

La propriété Discriminator est désormais nommée $type au lieu de Discriminator.

Suivi de problème n°34269

Ancien comportement

EF ajoute automatiquement une propriété discriminante aux documents JSON pour identifier le type d'entité que le document représente. Dans les versions précédentes de EF, cette propriété JSON était nommée Discriminator par défaut.

Nouveau comportement

Depuis EF Core 9.0, la propriété du discriminateur est désormais appelée $type par défaut. Si vous avez des documents existants dans Azure Cosmos DB provenant des versions précédentes d’EF, ceux-ci utilisent l’ancien nommage Discriminator, et après la mise à jour vers EF 9.0, les requêtes sur ces documents échoueront.

Pourquoi

Une pratique JSON émergente utilise une propriété $type dans les scénarios où le type d'un document doit être identifié. Par exemple, System.Text.Json de .NET prend également en charge le polymorphisme, en utilisant $type comme nom de propriété discriminante par défaut (docs). Pour s'aligner sur le reste de l'écosystème et faciliter l'interopérabilité avec des outils externes, la valeur par défaut a été modifiée.

Corrections

L'atténuation la plus simple consiste à configurer simplement le nom de la propriété du discriminateur pour qu'il soit Discriminator, comme précédemment :

modelBuilder.Entity<Session>().HasDiscriminator<string>("Discriminator");

En procédant ainsi pour tous vos types d'entités de premier niveau, EF se comportera comme auparavant.

À ce stade, si vous le souhaitez, vous pouvez également mettre à jour tous vos documents pour utiliser le nouveau nom $type.

La propriété id ne contient plus que la propriété EF key par défaut

Suivi de problème n°34179

Ancien comportement

Auparavant, EF a inséré la valeur du discriminant de votre type d'entité dans la propriété id du document. Par exemple, si vous avez enregistré un type d'entité Blog avec une propriété Id contenant 8, la propriété JSON id contiendra Blog|8.

Nouveau comportement

Depuis EF Core 9.0, la propriété JSON id ne contient plus la valeur du discriminateur, et contient uniquement la valeur de votre propriété clé. Pour l'exemple ci-dessus, la propriété JSON id serait simplement 8. Si vous avez des documents existants dans Azure Cosmos DB provenant de versions antérieures de EF, ceux-ci ont la valeur discriminator dans la propriété JSON id, et après la mise à jour vers EF 9.0, les requêtes sur ces documents échoueront.

Pourquoi

Étant donné que la propriété JSON id doit être unique, le discriminateur était précédemment ajouté pour permettre l’existence de différentes entités avec la même valeur clé. Par exemple, cela permettait d’avoir à la fois un Blog et un Post avec une propriété Id contenant la valeur 8 dans le même conteneur et partition. Cela correspondait mieux aux modèles de données des bases de données relationnelles, où chaque type d’entité est mappé à sa propre table, et dispose donc de son propre espace de clés.

EF 9.0 a généralement modifié le mapping pour être plus aligné avec les pratiques et attentes communes des bases de données NoSQL d’Azure Cosmos DB, plutôt que de correspondre aux attentes des utilisateurs venant de bases de données relationnelles. En outre, le fait que la valeur du discriminateur se trouve dans la propriété id complique l'interaction entre les outils et systèmes externes et les documents JSON générés par EF ; ces systèmes externes ne connaissent généralement pas les valeurs du discriminateur EF, qui sont par défaut dérivées des types .NET.

Corrections

L'atténuation la plus simple consiste à configurer simplement EF pour inclure le discriminant dans la propriété JSON id, comme précédemment. Une nouvelle option de configuration a été introduite à cet effet :

modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();

En procédant ainsi pour tous vos types d'entités de premier niveau, EF se comportera comme auparavant.

À ce stade, si vous le souhaitez, vous pouvez également mettre à jour tous vos documents pour réécrire leur propriété JSON id. Notez que cela n'est possible que si des entités de types différents ne partagent pas la même valeur ID au sein d'un même conteneur.

Changements à impact moyen

Les E/S synchrones via le fournisseur Azure Cosmos DB n’est plus pris en charge.

Suivi de problème n° 32563

Ancien comportement

Auparavant, l’appel de méthodes synchrones comme ToList ou SaveChanges entraînerait le blocage synchrone d’EF Core lorsque .GetAwaiter().GetResult() est utilisé pour exécuter les appels asynchrones sur le kit de développement logiciel (SDK) Azure Cosmos DB. Cela peut entraîner un interblocage.

Nouveau comportement

À compter de la version 9.0 d’EF Core, EF lève désormais une exception par défaut lors de la tentative d’utilisation d’E/S synchrones. Le message d’exception est « Azure Cosmos DB ne prend pas en charge les E/S synchrones. Veillez à utiliser et à attendre correctement les méthodes asynchrones uniquement lorsque vous utilisez Entity Framework Core pour accéder à Azure Cosmos DB. Pour plus d’informations, consultez https://aka.ms/ef-cosmos-nosync. »

Pourquoi

Le blocage synchrone sur les méthodes asynchrones peut entraîner un interblocage et le kit de développement logiciel (SDK) Azure Cosmos DB prend uniquement en charge les méthodes asynchrones.

Corrections

Dans la version 9.0 d’EF Core, l’erreur peut être supprimée avec :

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.ConfigureWarnings(w => w.Ignore(CosmosEventId.SyncNotSupported));
}

Cela étant dit, les applications doivent cesser d’utiliser des API synchrones avec Azure Cosmos DB, car elles ne sont pas prises en charge par le kit de développement logiciel (SDK) Azure Cosmos DB. La possibilité de supprimer l’exception sera supprimée dans une prochaine version d’EF Core, après quoi il sera uniquement possible d’utiliser des API asynchrones.

Les requêtes SQL doivent maintenant projeter directement des valeurs JSON

Suivi de problème n°25527

Ancien comportement

Auparavant, EF générait des requêtes telles que les suivantes :

SELECT c["City"] FROM root c

Ces requêtes amènent Azure Cosmos DB à encapsuler chaque résultat dans un objet JSON, comme suit :

[
    {
        "City": "Berlin"
    },
    {
        "City": "México D.F."
    }
]
Nouveau comportement

Depuis EF Core 9.0, EF ajoute désormais le modificateur VALUE aux requêtes comme suit :

SELECT VALUE c["City"] FROM root c

Ces requêtes amènent Azure Cosmos DB à retourner les valeurs directement, sans être encapsulées :

[
    "Berlin",
    "México D.F."
]

Si votre application utilise des requêtes SQL, il est probable que ces requêtes ne fonctionneront pas après la mise à jour vers EF 9.0, car elles n'incluent pas le modificateur VALUE.

Pourquoi

L’encapsulation de chaque résultat dans un objet JSON supplémentaire peut entraîner une dégradation des performances dans certains scénarios, gonfle la charge utile des résultats JSON, et ce n’est pas la façon naturelle de travailler avec Azure Cosmos DB.

Corrections

Pour y remédier, il suffit d'ajouter le modificateur VALUE aux projections de vos requêtes SQL, comme indiqué ci-dessus.

Les résultats non définis sont maintenant automatiquement filtrés des résultats de la requête.

Suivi de problème n°25527

Ancien comportement

Auparavant, EF générait des requêtes telles que les suivantes :

SELECT c["City"] FROM root c

Ces requêtes amènent Azure Cosmos DB à encapsuler chaque résultat dans un objet JSON, comme suit :

[
    {
        "City": "Berlin"
    },
    {
        "City": "México D.F."
    }
]

Si l'un des résultats n'était pas défini (par exemple, si la propriété City était absente du document), un document vide était renvoyé et EF renvoyait null pour ce résultat.

Nouveau comportement

Depuis EF Core 9.0, EF ajoute désormais le modificateur VALUE aux requêtes comme suit :

SELECT VALUE c["City"] FROM root c

Ces requêtes amènent Azure Cosmos DB à retourner les valeurs directement, sans être encapsulées :

[
    "Berlin",
    "México D.F."
]

Le comportement d’Azure Cosmos DB consiste à filtrer automatiquement les valeurs undefined des résultats. Cela signifie que si l’une des propriétés City est absente du document, la requête retournerait un seul résultat, plutôt que deux résultats, l’un étant null.

Pourquoi

L’encapsulation de chaque résultat dans un objet JSON supplémentaire peut entraîner une dégradation des performances dans certains scénarios, gonfle la charge utile des résultats JSON, et ce n’est pas la façon naturelle de travailler avec Azure Cosmos DB.

Corrections

Si l'obtention de valeurs null pour des résultats indéfinis est importante pour votre application, regroupez les valeurs undefined en null à l'aide du nouvel opérateur EF.Functions.Coalesce :

var users = await context.Customer
    .Select(c => EF.Functions.CoalesceUndefined(c.City, null))
    .ToListAsync();

Les requêtes traduites de manière incorrecte ne sont plus traduites.

Suivi de problème n°34123

Ancien comportement

Auparavant, EF traduisait des requêtes telles que la suivante :

var sessions = await context.Sessions
    .Take(5)
    .Where(s => s.Name.StartsWith("f"))
    .ToListAsync();

Cependant, la traduction SQL de cette requête était incorrecte :

SELECT c
FROM root c
WHERE ((c["Discriminator"] = "Session") AND STARTSWITH(c["Name"], "f"))
OFFSET 0 LIMIT @__p_0

En SQL, la clause WHERE est évaluée avant les clauses OFFSET et LIMIT ; mais dans la requête LINQ ci-dessus, l'opérateur Take apparaît avant l'opérateur Where. Par conséquent, de telles requêtes pouvaient renvoyer des résultats incorrects.

Nouveau comportement

Depuis EF Core 9.0, ces requêtes ne sont plus traduites et une exception est levée.

Pourquoi

Les traductions incorrectes peuvent entraîner une corruption silencieuse des données, ce qui peut introduire des bogues difficiles à détecter dans votre application. EF préfère toujours échouer rapidement en lançant l'opération dès le départ plutôt que de provoquer une éventuelle corruption des données.

Corrections

Si vous êtes satisfait du comportement précédent et que vous souhaitez exécuter le même code SQL, il vous suffit d'échanger l'ordre des opérateurs LINQ :

var sessions = await context.Sessions
    .Where(s => s.Name.StartsWith("f"))
    .Take(5)
    .ToListAsync();

Malheureusement, Azure Cosmos DB ne prend pas actuellement en charge les clauses OFFSET et LIMIT dans les sous-requêtes SQL, ce qui est requis pour la traduction correcte de la requête LINQ d’origine.

Modifications à faible impact

HasIndex est maintenant lancé au lieu d'être ignoré

Suivi de problème n°34023

Ancien comportement

Auparavant, les appels à HasIndex étaient ignorés par le fournisseur de la base de données EF Cosmos.

Nouveau comportement

Le fournisseur lance désormais un message si HasIndex est spécifié.

Pourquoi

Dans Azure Cosmos DB, toutes les propriétés sont indexées par défaut, et aucun indexage ne doit être spécifié. Bien qu'il soit possible de définir une stratégie d'indexation personnalisée, cela n'est pas actuellement pris en charge par EF, et peut être fait via le portail Azure sans le support d'EF. Étant donné que les appels HasIndex ne servaient à rien, ils ne sont plus autorisés.

Corrections

Supprimez tous les appels à HasIndex

IncludeRootDiscriminatorInJsonId a été renommé en HasRootDiscriminatorInJsonId après 9.0.0-rc.2

Suivi de problème n°34717

Ancien comportement

L’API IncludeRootDiscriminatorInJsonId a été introduite dans la version 9.0.0 rc.1.

Nouveau comportement

Pour la version finale de EF Core 9.0, l'API a été renommée en HasRootDiscriminatorInJsonId

Pourquoi

Une autre API connexe a été renommée pour commencer par Has au lieu de Include, et celle-ci a donc également été renommée pour des raisons de cohérence.

Corrections

Si votre code utilise l'API IncludeRootDiscriminatorInJsonId, il suffit de le remplacer par la référence HasRootDiscriminatorInJsonId.