Changements par rupture dans EF Core 7.0 (EF7)
Cette page documente les changements d’API et de comportement susceptibles d’interrompre les applications existantes qui mettent à jour EF Core 6 vers EF Core 7. 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 7.0 cible .NET 6. Cela signifie que les applications existantes qui ciblent .NET 6 peuvent continuer à le faire. Les applications ciblant les versions antérieures de .NET, .NET Core et .NET Framework devront cibler .NET 6 ou .NET 7 pour utiliser EF Core 7.0.
Résumé
Modifications à fort impact
Encrypt
devient true
par défaut pour les connexions SQL Server
Suivi du problème : SqlClient #1210
Important
Il s’agit d’un changement par rupture grave dans le package Microsoft.Data.SqlClient . Il n’y a rien à faire dans EF Core pour rétablir ou atténuer ce changement. Envoyez directement des commentaires au dépôt GitHub Microsoft.Data.SqlClient ou contactez un Microsoft Support Professionnel pour des questions ou de l’aide supplémentaires.
Ancien comportement
Les chaînes de connexion SqlClient utilisent Encrypt=False
par défaut. Cela permet d'établir des connexions sur des ordinateurs de développement lorsque le serveur local ne dispose pas d'un certificat valide.
Nouveau comportement
Les chaînes de connexion SqlClient utilisent Encrypt=True
par défaut. Ce qui signifie que :
- Le serveur doit être configuré avec un certificat valide.
- Le client doit approuver ce certificat
Si ces conditions ne sont pas remplies, une SqlException
sera levée. Par exemple :
Une connexion a été établie avec le serveur, mais une erreur s’est ensuite produite pendant le processus de connexion. (fournisseur : Fournisseur SSL, erreur : 0 - La chaîne de certificats a été émise par une autorité qui n’est pas approuvée.)
Pourquoi
Cette modification a été apportée pour vous assurer que, par défaut, la connexion est sécurisée ou l’application ne parvient pas à se connecter.
Corrections
Il y a trois manières de poursuivre :
- Installez un certificat valide sur le serveur. Notez qu'il s'agit d'un processus complexe qui nécessite d'obtenir un certificat et de s'assurer qu'il est signé par une autorité à laquelle le client fait confiance.
- Si le serveur dispose d’un certificat, mais qu’il n’est pas approuvé par le client, il
TrustServerCertificate=True
doit être autorisé à contourner le mécanisme de confiance normal. - Ajoutez explicitement
Encrypt=False
à la chaîne de connexion.
Avertissement
Les options 2 et 3 laissent le serveur dans un état potentiellement non sécurisé.
Certains avertissements lèvent à nouveau des exceptions par défaut
Ancien comportement
Dans EF Core 6.0, un bogue dans le fournisseur SQL Server signifiait que certains avertissements configurés pour lever des exceptions par défaut étaient plutôt enregistrés, mais ne levaient pas d’exceptions. Ces avertissements sont les suivants :
EventId | Description |
---|---|
RelationalEventId.AmbientTransactionWarning | Une application peut avoir attendu qu’une transaction ambiante soit utilisée lorsqu’elle a été réellement ignorée. |
RelationalEventId.IndexPropertiesBothMappedAndNotMappedToTable | Un index spécifie les propriétés dont certaines sont mappées et dont certaines ne sont pas mappées à une colonne d’une table. |
RelationalEventId.IndexPropertiesMappedToNonOverlappingTables | Un index spécifie les propriétés qui correspondent aux colonnes sur des tables qui ne se chevauchent pas. |
RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables | Une clé étrangère spécifie les propriétés qui ne sont pas mappées aux tables associées. |
Nouveau comportement
À compter d’EF Core 7.0, ces avertissements, par défaut, entraînent à nouveau la levée d’une exception.
Pourquoi
Il s’agit de problèmes qui indiquent très probablement une erreur dans le code de l’application qui doit être résolu.
Corrections
Corrigez le problème sous-jacent qui est la raison de l’avertissement.
Sinon, le niveau d’avertissement peut être modifié afin qu’il soit consigné uniquement ou supprimé entièrement. Par exemple :
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Ignore(RelationalEventId.AmbientTransactionWarning));
Les tables SQL Server avec des déclencheurs ou certaines colonnes calculées nécessitent désormais une configuration EF Core spéciale
Ancien comportement
Les versions précédentes du fournisseur SQL Server ont enregistré des modifications via une technique moins efficace qui a toujours fonctionné.
Nouveau comportement
Par défaut, EF Core enregistre désormais les modifications via une technique nettement plus efficace ; malheureusement, cette technique n'est pas prise en charge sur SQL Server si la table cible comporte des déclencheurs de base de données ou certains types de colonnes calculées. Consultez la documentation SQL Server pour plus d’informations.
Pourquoi
Les améliorations des performances liées à la nouvelle méthode sont suffisamment importantes pour en faire profiter les utilisateurs par défaut. En même temps, nous estimons que l’utilisation des déclencheurs de base de données ou des colonnes calculées affectées dans les applications EF Core est suffisamment faible pour que les conséquences négatives des changements par rupture soient compensées par le gain de performances.
Corrections
À compter d’EF Core 8.0, l’utilisation ou non de la clause « OUTPUT » peut être configurée explicitement. Par exemple :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.ToTable(tb => tb.UseSqlOutputClause(false));
}
Dans EF7 ou version ultérieure, si la table cible a un déclencheur, vous pouvez indiquer à EF Core cela, et EF revient à la technique précédente, moins efficace. Pour ce faire, configurez le type d’entité correspondant comme suit :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.ToTable(tb => tb.HasTrigger("SomeTrigger"));
}
Notez que cette opération n’entraîne pas la création ou la gestion du déclencheur EF Core de quelque manière que ce soit. Elle informe actuellement uniquement EF Core que les déclencheurs sont présents sur la table. Par conséquent, n’importe quel nom de déclencheur peut être utilisé. La spécification d’un déclencheur peut être utilisée pour rétablir l’ancien comportement même s’il n’existe pas réellement de déclencheur dans la table.
Si la plupart ou toutes vos tables ont des déclencheurs, vous pouvez refuser d’utiliser la technique plus récente et efficace pour toutes les tables de votre modèle à l’aide de la convention de création de modèles suivante :
public class BlankTriggerAddingConvention : IModelFinalizingConvention
{
public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
if (table != null
&& entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(table.Value) == null)
&& (entityType.BaseType == null
|| entityType.GetMappingStrategy() != RelationalAnnotationNames.TphMappingStrategy))
{
entityType.Builder.HasTrigger(table.Value.Name + "_Trigger");
}
foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
{
if (entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(fragment.StoreObject) == null))
{
entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger");
}
}
}
}
}
Utilisez la convention sur votre DbContext
en remplaçant ConfigureConventions
:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Add(_ => new BlankTriggerAddingConvention());
}
Cela appelle efficacement HasTrigger
sur toutes les tables de votre modèle, au lieu de devoir le faire manuellement pour chaque table.
Les tables SQLite avec des déclencheurs AFTER et des tables virtuelles nécessitent désormais une configuration EF Core spéciale
Ancien comportement
Les versions précédentes du fournisseur SQL Server ont enregistré des modifications via une technique moins efficace qui a toujours fonctionné.
Nouveau comportement
Par défaut, EF Core enregistre désormais les modifications via une technique plus efficace, à l’aide de la clause RETURNING. Malheureusement, cette technique n’est pas prise en charge sur SQLite si la table cible a des déclencheurs AFTER de base de données, est virtuelle ou si des versions antérieures de SQLite sont utilisées. Pour plus d’informations, consultez la documentation SQLite.
Pourquoi
Les simplifications et les améliorations des performances liées à la nouvelle méthode sont suffisamment importantes pour en faire profiter les utilisateurs par défaut. En même temps, nous estimons que l’utilisation des déclencheurs de base de données et de tables virtuelles dans les applications EF Core est suffisamment faible pour que les conséquences négatives des changements par rupture soient compensées par le gain de performances.
Corrections
Dans EF Core 8.0, la méthode UseSqlReturningClause
a été introduite pour revenir explicitement à l’ancienne, moins efficace SQL. Par exemple :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.ToTable(tb => tb.UseSqlReturningClause(false));
}
Si vous utilisez toujours EF Core 7.0, il est possible de revenir à l’ancien mécanisme de l’application entière en insérant le code suivant dans votre configuration de contexte :
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlite(...)
.ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();
Changements à impact moyen
Les dépendances orphelines des relations facultatives ne sont pas automatiquement supprimées
Ancien comportement
Une relation est facultative si sa clé étrangère est nullable. La définition de la clé étrangère sur Null permet à l’entité dépendante d’exister sans entité principale associée. Les relations facultatives peuvent être configurées pour utiliser les suppressions en cascade, bien qu’il ne s’agit pas de la valeur par défaut.
Une dépendance facultative peut être supprimée de son principal en définissant sa clé étrangère sur Null, ou en désactivant la navigation vers ou à partir de celle-ci. Dans EF Core 6.0, cela entraînerait la suppression de la dépendance lorsque la relation a été configurée pour la suppression en cascade.
Nouveau comportement
À compter d’EF Core 7.0, la dépendance n’est plus supprimée. Notez que si le principal est supprimé, le dépendant est toujours supprimé, car les suppressions en cascade sont configurées pour la relation.
Pourquoi
La dépendance peut exister sans relation avec un principal. Par conséquent, la suppression de la relation ne doit pas entraîner la suppression de l’entité.
Corrections
La dépendance peut être supprimée explicitement :
context.Remove(blog);
Ou SaveChanges
peut être substitué ou intercepté pour supprimer des dépendants sans référence de principal. Par exemple :
context.SavingChanges += (c, _) =>
{
foreach (var entry in ((DbContext)c!).ChangeTracker
.Entries<Blog>()
.Where(e => e.State == EntityState.Modified))
{
if (entry.Reference(e => e.Author).CurrentValue == null)
{
entry.State = EntityState.Deleted;
}
}
};
La suppression en cascade est configurée entre les tables lors de l’utilisation du mappage TPT avec SQL Server
Ancien comportement
Lorsque du mappage d’une hiérarchie d’héritage à l’aide de la stratégie TPT, la table de base doit contenir une ligne pour chaque entité enregistrée, quel que soit le type réel de cette entité. La suppression de la ligne dans la table de base doit supprimer des lignes dans toutes les autres tables. EF Core configure une suppression en cascade pour cela.
Dans EF Core 6.0, un bogue dans le fournisseur de base de données SQL Server signifiait que ces suppressions en cascade n’étaient pas créées.
Nouveau comportement
À compter d’EF Core 7.0, les suppressions en cascade sont désormais créées pour SQL Server comme elles l’étaient toujours pour les autres bases de données.
Pourquoi
Les suppressions en cascade de la table de base vers les sous-tables de TPT permettent à une entité d’être supprimée en supprimant sa ligne dans la table de base.
Corrections
Dans la plupart des cas, cette modification ne doit pas provoquer de problèmes. Toutefois, SQL Server est très restrictif lorsqu’il existe plusieurs comportements en cascade configurés entre les tables. Cela signifie que s’il existe une relation en cascade existante entre les tables du mappage TPT, SQL Server peut générer l’erreur suivante :
Microsoft.Data.SqlClient.SqlException : L’instruction DELETE est en conflit avec la contrainte REFERENCE « FK_Blogs_People_OwnerId ». Le conflit s’est produit dans la base de données « Scratch », la table « dbo.Blogs », la colonne « OwnerId ». L'instruction a été arrêtée.
Par exemple, ce modèle crée un cycle de relations en cascade :
[Table("FeaturedPosts")]
public class FeaturedPost : Post
{
public int ReferencePostId { get; set; }
public Post ReferencePost { get; set; } = null!;
}
[Table("Posts")]
public class Post
{
public int Id { get; set; }
public string? Title { get; set; }
public string? Content { get; set; }
}
L’une de ces opérations doit être configurée pour ne pas utiliser de suppressions en cascade sur le serveur. Par exemple, pour modifier la relation explicite :
modelBuilder
.Entity<FeaturedPost>()
.HasOne(e => e.ReferencePost)
.WithMany()
.OnDelete(DeleteBehavior.ClientCascade);
Ou pour modifier la relation implicite créée pour le mappage TPT :
modelBuilder
.Entity<FeaturedPost>()
.HasOne<Post>()
.WithOne()
.HasForeignKey<FeaturedPost>(e => e.Id)
.OnDelete(DeleteBehavior.ClientCascade);
Risque plus élevé d’erreurs de disponibilité/verrouillage sur SQLite lorsque vous n’utilisez pas la journalisation à écriture anticipée
Ancien comportement
Les versions précédentes du fournisseur SQLite ont enregistré des modifications via une technique moins efficace qui a pu réessayer automatiquement lorsque la table a été verrouillée/occupée et que la journalisation à écriture anticipée (WAL) n’a pas été activée.
Nouveau comportement
Par défaut, EF Core enregistre désormais les modifications via une technique plus efficace, à l’aide de la clause RETURNING. Cette technique n’est malheureusement pas en mesure de réessayer automatiquement lorsqu’elle est occupée/verrouillée. Dans une application multithread (telle qu’une application web) qui n’utilise pas la journalisation à écriture anticipée, il est courant de rencontrer ces erreurs.
Pourquoi
Les simplifications et les améliorations des performances liées à la nouvelle méthode sont suffisamment importantes pour en faire profiter les utilisateurs par défaut. Les bases de données créées par EF Core activent également la journalisation à écriture anticipée par défaut. L’équipe SQLite recommande également d’activer la journalisation à écriture anticipée par défaut.
Corrections
Si possible, vous devez activer la journalisation à écriture anticipée sur votre base de données. Si votre base de données a été créée par EF, cela doit déjà être le cas. Si ce n’est pas le cas, vous pouvez activer la journalisation à écriture anticipée en exécutant la commande suivante.
PRAGMA journal_mode = 'wal';
Si, pour une raison quelconque, vous ne pouvez pas activer la journalisation à écriture anticipée, il est possible de revenir à l’ancien mécanisme de l’application entière en insérant le code suivant dans votre configuration de contexte :
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlite(...)
.ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();
Modifications à faible impact
Les propriétés de clé peuvent avoir besoin d’être configurées avec un comparateur de valeurs de fournisseur
Ancien comportement
Dans EF Core 6.0, les valeurs de clé extraites directement des propriétés des types d’entités ont été utilisées pour la comparaison des valeurs de clé lors de l’enregistrement des modifications. Cela utilise n’importe quel comparateur de valeurs personnalisé configuré sur ces propriétés.
Nouveau comportement
À compter d’EF Core 7.0, les valeurs de base de données sont utilisées pour ces comparaisons. Cela « fonctionne juste » pour la grande majorité des cas. Toutefois, si les propriétés utilisaient un comparateur personnalisé et que ce comparateur ne peut pas être appliqué aux valeurs de base de données, un « comparateur de valeurs de fournisseur » peut être nécessaire, comme indiqué ci-dessous.
Pourquoi
Différents fractionnements d’entités et fractionnements de tables peuvent entraîner plusieurs propriétés mappées à la même colonne de base de données, et inversement. Cela nécessite la comparaison des valeurs après la conversion en valeur qui sera utilisée dans la base de données.
Corrections
Configurez un comparateur de valeurs de fournisseur. Par exemple, considérez le cas où un objet valeur est utilisé comme clé, et le comparateur pour cette clé utilise des comparaisons de chaînes qui ne respectent pas la casse :
var blogKeyComparer = new ValueComparer<BlogKey>(
(l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
v => v.Id.ToUpper().GetHashCode(),
v => v);
var blogKeyConverter = new ValueConverter<BlogKey, string>(
v => v.Id,
v => new BlogKey(v));
modelBuilder.Entity<Blog>()
.Property(e => e.Id).HasConversion(
blogKeyConverter, blogKeyComparer);
Les valeurs de base de données (chaînes) ne peuvent pas utiliser directement le comparateur défini pour les types BlogKey
. Par conséquent, un comparateur de fournisseurs pour les comparaisons de chaînes non sensibles à la casse doit être configuré :
var caseInsensitiveComparer = new ValueComparer<string>(
(l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
v => v.ToUpper().GetHashCode(),
v => v);
var blogKeyComparer = new ValueComparer<BlogKey>(
(l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
v => v.Id.ToUpper().GetHashCode(),
v => v);
var blogKeyConverter = new ValueConverter<BlogKey, string>(
v => v.Id,
v => new BlogKey(v));
modelBuilder.Entity<Blog>()
.Property(e => e.Id).HasConversion(
blogKeyConverter, blogKeyComparer, caseInsensitiveComparer);
Les contraintes de vérification et d’autres facettes de table sont désormais configurées sur la table
Ancien comportement
Dans EF Core 6.0, HasCheckConstraint
, HasComment
et IsMemoryOptimized
ont été appelés directement sur le générateur de types d’entité. Par exemple :
modelBuilder
.Entity<Blog>()
.HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023");
modelBuilder
.Entity<Blog>()
.HasComment("It's my table, and I'll delete it if I want to.");
modelBuilder
.Entity<Blog>()
.IsMemoryOptimized();
Nouveau comportement
À compter d’EF Core 7.0, ces méthodes sont appelées à la place sur le générateur de tables :
modelBuilder
.Entity<Blog>()
.ToTable(b => b.HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023"));
modelBuilder
.Entity<Blog>()
.ToTable(b => b.HasComment("It's my table, and I'll delete it if I want to."));
modelBuilder
.Entity<Blog>()
.ToTable(b => b.IsMemoryOptimized());
Ces méthodes ont été marquées comme Obsolete
. Elles ont actuellement le même comportement que les nouvelles méthodes, mais seront supprimées dans une prochaine version.
Pourquoi
Ces facettes s’appliquent uniquement aux tables. Elles ne seront appliquées à aucune vue, fonctions ou procédures stockées mappées.
Corrections
Utilisez les méthodes du générateur de tables, comme indiqué ci-dessus.
Les navigations de nouvelles entités vers des entités supprimées ne sont pas corrigées
Ancien comportement
Dans EF Core 6.0, lorsqu’une nouvelle entité est suivie à partir d’une de requête de suivi ou en l’attachant au DbContext
, les navigations vers et à partir d’entités associées dans Deleted
l’état sont corrigées.
Nouveau comportement
À compter d’EF Core 7.0, les navigations vers et depuis Deleted
entités ne sont pas corrigées.
Pourquoi
Une fois qu’une entité est marquée comme Deleted
, il est rarement judicieux de l’associer à des entités non supprimées.
Corrections
Interrogez ou attachez des entités avant de marquer des entités en tant que Deleted
, ou définissez manuellement les propriétés de navigation vers et à partir de l’entité supprimée.
L’utilisation de FromSqlRaw
et les méthodes associées du fournisseur incorrect lève l’exception utiliser-la-méthode-appropriée
Ancien comportement
Dans EF Core 6.0, l’utilisation de la méthode d’extension FromSqlRaw Azure Cosmos DB lors de l’utilisation d’un fournisseur relationnel ou de la méthode d’extension de FromSqlRaw relationnelle lors de l’utilisation du fournisseur Azure Cosmos DB peut échouer en mode silencieux. De même, l’utilisation de méthodes relationnelles sur le fournisseur en mémoire est une opération sans opération silencieuse.
Nouveau comportement
À compter d’EF Core 7.0, l’utilisation d’une méthode d’extension conçue pour un fournisseur sur un autre fournisseur lève une exception.
Pourquoi
La méthode d’extension correcte doit être utilisée pour qu’elle fonctionne correctement dans toutes les situations.
Corrections
Utilisez la méthode d’extension correcte pour le fournisseur utilisé. Si plusieurs fournisseurs sont référencés, appelez la méthode d’extension en tant que méthode statique. Par exemple :
var result = CosmosQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();
Ou :
var result = RelationalQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();
OnConfiguring
généré automatiquement n’appelle plus IsConfigured
Ancien comportement
Dans EF Core 6.0, le type de DbContext
généré à partir d’une base de données existante contenait un appel à IsConfigured
. Par exemple :
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
optionsBuilder.UseNpgsql("MySecretConnectionString");
}
}
Nouveau comportement
À compter d’EF Core 7.0, l’appel à IsConfigured
n’est plus inclus.
Pourquoi
Il existe des scénarios très limités où le fournisseur de base de données est configuré dans votre DbContext dans certains cas, mais uniquement si le contexte n’est pas déjà configuré. Au lieu de cela, laissant OnConfiguring
ici rend plus probable qu’une chaîne de connexion contenant des informations sensibles soit laissée dans le code, malgré l’avertissement au moment de la compilation. Ainsi, le code supplémentaire et plus propre de la suppression de ce code a été jugé utile, en particulier étant donné que l’indicateur --no-onconfiguring
(CLI .NET) ou -NoOnConfiguring
(console du Gestionnaire de package Visual Studio) peut être utilisé pour empêcher la génération automatique de la méthode OnConfiguring
, et que les modèles personnalisables existent pour ajouter IsConfigured
s’il est vraiment nécessaire.
Corrections
Un des deux éléments suivants :
- Utilisez l’argument
--no-onconfiguring
(cli.NET) ou-NoOnConfiguring
(console du Gestionnaire de package Visual Studio) lors de la génération de modèles automatique à partir d’une base de données existante. - Personnalisez les modèles T4 pour rajouter l’appel à
IsConfigured
.