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 :
- Changements cassants dans EF Core 8
- Changements cassants dans EF Core 7
- Changements cassants dans EF Core 6
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[]?
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
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
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.
Modifications à fort impact
La propriété Discriminator est désormais nommée $type
au lieu de Discriminator
.
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
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.
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
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.
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.
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é
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
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
.