Changements cassants dans EF Core 5.0

Les changements de comportement et d’API suivants peuvent interrompre la mise à jour des applications existantes vers EF Core 5.0.0.

Résumé

Modification critique Impact
EF Core 5.0 ne prend pas en charge .NET Framework Moyenne
IProperty.GetColumnName() est désormais obsolète Moyenne
La précision et l’échelle sont requises pour les décimales Moyenne
La navigation obligatoire ou non-nullable entre le principal et le dépendant a une sémantique différente Moyenne
La définition de la requête est remplacée par des méthodes spécifiques au fournisseur Moyenne
Les navigations de référence non null ne sont pas remplacées par les requêtes Moyenne
ToView () est traité différemment par les migrations Moyenne
ToTable (null) marque le type d’entité comme non mappé à une table Moyenne
Suppression de la méthode HasGeometricDimension de l’extension NTS SQLite Faible
Azure Cosmos DB : la clé de partition est désormais ajoutée à la clé primaire Faible
Azure Cosmos DB : propriété id renommée __id Faible
Azure Cosmos DB : byte[] est maintenant stocké en tant que chaîne base64 au lieu d’un tableau de nombres Faible
Azure Cosmos DB : GetPropertyName et SetPropertyName ont été renommés Faible
Les générateurs de valeur sont appelés lorsque l’état de l’entité passe de Detached à Unchanged, Updated ou Deleted Faible
IMigrationsModelDiffer utilise désormais IRelationalModel Faible
Les discriminateurs sont en lecture seule Faible
Les méthodes EF.Functions spécifiques au fournisseur lèvent une exception pour le fournisseur InMemory Faible
IndexBuilder.HasName est désormais obsolète Faible
Un pluraliseur est maintenant inclus pour l’échafaudage de modèles ayant fait l’objet d’une ingénierie à rebours Faible
INavigationBase remplace INavigation dans certaines API pour prendre en charge les navigations de redirection Faible
Certaines requêtes avec une collection corrélée qui utilisent également Distinct ou GroupBy ne sont plus prises en charge Faible
L’utilisation d’une collection de type requêtable dans la projection n’est pas prise en charge Faible

Changements à impact moyen

EF Core 5.0 ne prend pas en charge .NET Framework

Suivi de problème no 15498

Ancien comportement

EF Core 3.1 cible .NET Standard 2.0, qui est pris en charge par .NET Framework.

Nouveau comportement

EF Core 5.0 cible .NET Standard 2.1, qui n’est pas pris en charge par .NET Framework. Cela signifie qu’EF Core 5.0 ne peut pas être utilisé avec les applications .NET Framework.

Pourquoi

Cela fait partie d’une évolution plus étendue entre les équipes .NET, visant à l’unification vers une version cible unique de .NET Framework. Pour plus d’informations, consultez l’article consacré à l’avenir de .NET Standard.

Corrections

Les applications .NET Framework peuvent continuer à utiliser EF Core 3.1, qui est une version de support à long terme (LTS). Les applications peuvent par ailleurs être mises à jour de façon à utiliser .NET Core 3.1 ou .NET 5, qui prennent toutes deux en charge .NET Standard 2.1.

IProperty.GetColumnName() est désormais obsolète

Suivi de problème n° 2266

Ancien comportement

GetColumnName() retournait le nom de la colonne à laquelle la propriété était mappée.

Nouveau comportement

GetColumnName() retourne toujours le nom d’une colonne à laquelle une propriété est mappée, mais ce comportement est désormais ambigu, car EF Core 5 prend en charge TPT et le mappage simultané à une vue ou une fonction dans laquelle ces mappages peuvent utiliser des noms de colonne différents pour la même propriété.

Pourquoi

Nous avons marqué cette méthode comme obsolète pour guider les utilisateurs vers une surcharge plus précise : GetColumnName(IProperty, StoreObjectIdentifier).

Corrections

Si le type d’entité n’est jamais mappé qu’à une seule table et jamais à des vues, des fonctions ou plusieurs tables, la surcharge GetColumnBaseName(IReadOnlyProperty) peut être utilisée dans EF Core 5.0 et 6.0 pour obtenir le nom de la table. Par exemple :

var columnName = property.GetColumnBaseName();

Dans EF Core 7.0, elle peut à nouveau être remplacée par la nouvelle méthode GetColumnName, qui a le même comportement que l’original pour les mappages simples et de tables uniques.

Si le type d’entité peut être mappé à des vues, des fonctions ou plusieurs tables, StoreObjectIdentifier doit être obtenu pour identifier la table, la vue ou la fonction. Vous pouvez ensuite l’utiliser pour obtenir le nom de colonne de cet objet store. Par exemple :

var columnName = property.GetColumnName(StoreObjectIdentifier.Table("Users", null)));

La précision et l’échelle sont requises pour les décimales

Suivi de problème n° 19293

Ancien comportement

EF Core n’a normalement pas défini la précision et la mise à l’échelle sur des objets SqlParameter. Cela signifie que la précision et l’échelle complètes ont été envoyées à SQL Server, auquel cas ce dernier effectue un arrondi en fonction de la précision et de l’échelle de la colonne de base de données.

Nouveau comportement

EF Core définit désormais la précision et l’échelle des paramètres à l’aide des valeurs configurées pour les propriétés dans le modèle EF Core. Cela signifie que l’arrondi s’effectue désormais dans SqlClient. En conséquence, si la précision et l’échelle configurées ne correspondent pas à celles de la base de données, l’arrondi obtenu peut changer.

Pourquoi

Les nouvelles fonctionnalités de SQL Server, y compris Always Encrypted, requièrent que les facettes de paramètres soient entièrement spécifiées. En outre, SqlClient a apporté une modification de manière à arrondir les valeurs décimales plutôt que les tronquer, ce qui correspond au comportement de SQL Server. Cela permet à EF Core de définir ces facettes sans modifier le comportement des décimales correctement configurées.

Corrections

Mappez vos propriétés décimales à l’aide d’un nom de type qui comprend la précision et l’échelle. Par exemple :

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

    [Column(TypeName = "decimal(16, 5)")]
    public decimal Score { get; set; }
}

Vous pouvez également utiliser HasPrecision dans les API de génération de modèles. Par exemple :

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().Property(e => e.Score).HasPrecision(16, 5);
    }

La navigation obligatoire ou non-nullable entre le principal et le dépendant a une sémantique différente

Suivi de problème n° 17286

Ancien comportement

Seules les navigations vers le principal peuvent être configurées selon les besoins. Par conséquent, l’utilisation de RequiredAttribute sur la navigation vers le dépendant (l’entité qui contient la clé étrangère) ou la marquer comme non-nullable crée la clé étrangère sur le type d’entité de définition.

Nouveau comportement

Avec l’ajout de la prise en charge des dépendances obligatoires, il est désormais possible de marquer toute navigation de référence comme obligatoire, ce qui signifie que dans le cas illustré ci-dessus, la clé étrangère sera définie de l’autre côté de la relation, et les propriétés ne seront pas marquées comme obligatoires.

L’appel de IsRequired avant de spécifier l’extrémité dépendante est désormais ambigu :

modelBuilder.Entity<Blog>()
    .HasOne(b => b.BlogImage)
    .WithOne(i => i.Blog)
    .IsRequired()
    .HasForeignKey<BlogImage>(b => b.BlogForeignKey);

Pourquoi

Le nouveau comportement est nécessaire pour permettre la prise en charge des dépendants obligatoires (voir n° 12100).

Corrections

Supprimez RequiredAttribute de la navigation vers le dépendant et placez-le à la place sur la navigation vers le principal ou configurez la relation dans OnModelCreating :

modelBuilder.Entity<Blog>()
    .HasOne(b => b.BlogImage)
    .WithOne(i => i.Blog)
    .HasForeignKey<BlogImage>(b => b.BlogForeignKey)
    .IsRequired();

La définition de la requête est remplacée par des méthodes spécifiques au fournisseur

Suivi de problème n° 18903

Ancien comportement

Les types d’entité ont été mappés avec la définition des requêtes au niveau du noyau. Chaque fois que le type d’entité a été utilisé dans la requête, la racine du type d’entité a été remplacée par la requête de définition pour n’importe quel fournisseur.

Nouveau comportement

Les API permettant de définir des requêtes sont obsolètes. De nouvelles API spécifiques au fournisseur ont été introduites.

Pourquoi

Bien que des requêtes de définition aient été mises en œuvre en tant que requêtes de remplacement chaque fois que la racine de la requête était utilisée dans la requête, cette approche présentait quelques problèmes :

  • Si la requête de définition projette le type d’entité à l’aide de new { ... } dans la méthode Select, l’identification en tant qu’entité a requis un travail supplémentaire et est incohérente avec la façon dont EF Core traite les types nominaux dans la requête.
  • Pour les fournisseurs relationnels, FromSql est toujours nécessaire pour transmettre la chaîne SQL sous forme d’expression LINQ.

Au départ, les requêtes de définition ont été introduites en tant que vues côté client à utiliser avec le fournisseur InMemory pour les entités sans clé (similaires aux vues de base de données dans les bases de données relationnelles). Une telle définition facilite le test de l’application par rapport à une base de données en mémoire. Par la suite, elles sont devenues largement applicables, ce qui était utile, mais entraînait des comportements incohérents et difficiles à comprendre. Nous avons donc décidé de simplifier le concept. Nous avons fait en sorte que les requêtes de définition basées sur LINQ soient exclusives au fournisseur InMemory et qu’elles soient traitées différemment. Pour plus d’informations, consultez ce problème.

Corrections

Pour les fournisseurs relationnels, utilisez la méthode ToSqlQuery dans OnModelCreating et transmettez une chaîne SQL à utiliser pour le type d’entité. Pour le fournisseur InMemory, utilisez la méthode ToInMemoryQuery dans OnModelCreating et transmettez une requête LINQ à utiliser pour le type d’entité.

Les navigations de référence non nulles ne sont pas remplacées par les requêtes

Suivi de problème n° 2693

Ancien comportement

Dans EF Core 3.1, les navigations de référence initialisées avec empressement avec des valeurs non nulles étaient parfois remplacées par des instances d’entités provenant de la base de données, que les valeurs des clés correspondent ou non. Toutefois, dans d’autres cas, EF Core 3.1 ferait l’inverse et laisserait la valeur non null existante.

Nouveau comportement

Depuis EF Core 5.0, les navigations de référence non null ne sont jamais remplacées par les instances retournées par une requête.

Notez qu’une initialisation hâtive d’une navigation de collection vers une collection vide est toujours prise en charge.

Pourquoi

L’initialisation d’une propriété de navigation de référence en une instance d’entité « vide » produit un état ambigu. Par exemple :

public class Blog
{
     public int Id { get; set; }
     public Author Author { get; set; ) = new Author();
}

Normalement, une requête pour les blogs et les auteurs créera d’abord des instances Blog, puis définira les instances Author appropriées en fonction des données retournées par la base de données. Toutefois, dans ce cas, chaque propriété Blog.Author est déjà initialisée sur un Author vide. Cependant, EF Core n’a aucun moyen de savoir que cette instance est « vide ». Par conséquent, le remplacement de cette instance pourrait donc potentiellement détruire par inadvertance un Author valide. Par conséquent, EF Core 5.0 n’écrase plus systématiquement une navigation déjà initialisée.

Ce nouveau comportement s’aligne également sur celui d’EF6 dans la plupart des cas, bien qu’après enquête, nous ayons également trouvé quelques incohérences dans EF6.

Corrections

Si ce problème se produit, il convient d’arrêter toute initialisation hâtive des propriétés de navigation de référence.

ToView () est traité différemment par les migrations

Suivi de problème n° 2725

Ancien comportement

L’appel à ToView(string) a fait en sorte que les migrations ignorent le type d’entité en plus de le mapper à une vue.

Nouveau comportement

Désormais, ToView(string) marque le type d’entité comme non mappé à une table en plus de la mapper à une vue. Il en résulte que lors de la première migration après la mise à niveau vers EF Core 5, on essaie de supprimer la table par défaut pour ce type d’entité, car elle n’est plus ignorée.

Pourquoi

EF Core permet désormais à un type d’entité d’être mappé simultanément à une table et à une vue, de sorte que ToView n’est plus un indicateur valable pour déterminer que cet élément doit être ignoré lors des migrations.

Corrections

Utilisez le code suivant pour marquer la table mappée comme exclue des migrations :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().ToTable("UserView", t => t.ExcludeFromMigrations());
}

ToTable (null) marque le type d’entité comme non mappé à une table

Suivi de problème n° 21172

Ancien comportement

ToTable(null) rétablit le nom de la table par défaut.

Nouveau comportement

ToTable(null) marque désormais le type d’entité comme n’étant mappé à aucune table.

Pourquoi

EF Core permet désormais à un type d’entité d’être mappé simultanément à une table et à une vue, de sorte que ToTable(null) est utilisé pour indiquer qu’il n’est mappé à aucune table.

Corrections

Utilisez le code suivant pour réinitialiser le nom de la table à sa valeur par défaut si elle n’est mappée à aucune vue ou DbFunction :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().Metadata.RemoveAnnotation(RelationalAnnotationNames.TableName);
}

Modifications à faible impact

Suppression de la méthode HasGeometricDimension de l’extension NTS SQLite

Suivi de problème n° 14257

Ancien comportement

HasGeometricDimension a été utilisé pour activer des dimensions supplémentaires (Z et M) sur les colonnes géométriques. Cependant, cet élément n’a affecté que la création de bases de données. Il n’était pas nécessaire de le spécifier pour interroger des valeurs comportant des dimensions supplémentaires. Il ne fonctionnait pas non plus correctement lors de l’insertion ou de la mise à jour de valeurs comportant des dimensions supplémentaires (voir n° 14257).

Nouveau comportement

Pour permettre l’insertion et la mise à jour de valeurs géométriques avec des dimensions supplémentaires (Z et M), la dimension doit être spécifiée dans le nom du type de colonne. Cette API correspond mieux au comportement sous-jacent de la fonction AddGeometryColumn de SpatiaLite.

Pourquoi

L’utilisation de HasGeometricDimension après avoir spécifié la dimension dans le type de colonne est inutile et redondante, c’est pourquoi nous avons entièrement supprimé cet élément.

Corrections

Utilisez HasColumnType pour spécifier la dimension :

modelBuilder.Entity<GeoEntity>(
    x =>
    {
        // Allow any GEOMETRY value with optional Z and M values
        x.Property(e => e.Geometry).HasColumnType("GEOMETRYZM");

        // Allow only POINT values with an optional Z value
        x.Property(e => e.Point).HasColumnType("POINTZ");
    });

Azure Cosmos DB : la clé de partition est désormais ajoutée à la clé primaire

Suivi de problème n° 15289

Ancien comportement

La propriété de la clé de partition n’a été ajoutée qu’à la clé alternative qui comprend id.

Nouveau comportement

La propriété de la clé de partition est désormais également ajoutée à la clé primaire par convention.

Pourquoi

Cette modification permet de mieux aligner le modèle sur la sémantique d’Azure Cosmos DB et améliore les performances de Find et de certaines requêtes.

Corrections

Pour empêcher que la propriété de la clé de partition soit ajoutée à la clé primaire, configurez-la dans OnModelCreating.

modelBuilder.Entity<Blog>()
    .HasKey(b => b.Id);

Azure Cosmos DB : propriété id renommée __id

Suivi de problème n° 17751

Ancien comportement

La propriété cachée mappée à la propriété JSON id a également été nommée id.

Nouveau comportement

La propriété cachée créée par convention est maintenant nommée __id.

Pourquoi

Cette modification réduit le risque de conflit entre la propriété id et une propriété existante du type d’entité.

Corrections

Pour revenir au comportement 3.x, configurez la propriété id dans OnModelCreating.

modelBuilder.Entity<Blog>()
    .Property<string>("id")
    .ToJsonProperty("id");

Azure Cosmos DB : byte[] est maintenant stocké en tant que chaîne base64 au lieu d’un tableau de nombres

Suivi de problème n° 17306

Ancien comportement

Les propriétés de type byte[] étaient stockées sous la forme d’un tableau de nombres.

Nouveau comportement

Elles le sont désormais stockées sous la forme d’une chaîne base64.

Pourquoi

Cette représentation de byte[] correspond mieux aux attentes et constitue le comportement par défaut des principales bibliothèques de sérialisation JSON.

Corrections

Les données existantes stockées sous forme de tableaux de nombres seront toujours interrogées correctement, mais il n’existe actuellement aucun moyen de revenir sur le comportement d’insertion. Si cette limitation bloque votre scénario, publiez un commentaire sur ce problème.

Azure Cosmos DB : GetPropertyName et SetPropertyName ont été renommés

Suivi de problème n° 17874

Ancien comportement

Auparavant, les méthodes d’extension étaient appelées GetPropertyName et SetPropertyName.

Nouveau comportement

L’ancienne API a été supprimée et de nouvelles méthodes ont été ajoutées : GetJsonPropertyName, SetJsonPropertyName.

Pourquoi

Cette modification supprime l’ambiguïté sur ce que ces méthodes configurent.

Corrections

Utilisez la nouvelle API.

Les générateurs de valeur sont appelés lorsque l’état de l’entité passe de Detached à Unchanged, Updated ou Deleted

Suivi de problème n° 15289

Ancien comportement

Les générateurs de valeur n’étaient appelés que lorsque l’état de l’entité passait à Ajouté.

Nouveau comportement

Les générateurs de valeurs sont désormais appelés lorsque l’état de l’entité passe de Detached à Unchanged, Updated ou Deleted et que la propriété contient les valeurs par défaut.

Pourquoi

Cette modification était nécessaire pour améliorer l’expérience avec les propriétés qui ne sont pas persistantes dans le magasin de données et dont la valeur est toujours générée sur le client.

Corrections

Pour éviter que le générateur de valeur ne soit appelé, attribuez une valeur autre que celle par défaut à la propriété avant que l’état ne change.

IMigrationsModelDiffer utilise désormais IRelationalModel

Suivi de problème n° 20305

Ancien comportement

L’API IMigrationsModelDiffer a été définie à l’aide d’IModel.

Nouveau comportement

L’API IMigrationsModelDiffer utilise désormais IRelationalModel. Cependant, l’instantané du modèle ne contient toujours qu’IModel, car ce code fait partie de l’application et Entity Framework ne peut pas le modifier sans effectuer un changement cassant.

Pourquoi

IRelationalModel est une nouvelle représentation du schéma de la base de données. Cet élément permet de trouver plus rapidement et précisément les différences.

Corrections

Utilisez le code suivant pour comparer le modèle de snapshot au modèle de context :

var dependencies = context.GetService<ProviderConventionSetBuilderDependencies>();
var relationalDependencies = context.GetService<RelationalConventionSetBuilderDependencies>();

var typeMappingConvention = new TypeMappingConvention(dependencies);
typeMappingConvention.ProcessModelFinalizing(((IConventionModel)modelSnapshot.Model).Builder, null);

var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies);
var sourceModel = relationalModelConvention.ProcessModelFinalized(snapshot.Model);

var modelDiffer = context.GetService<IMigrationsModelDiffer>();
var hasDifferences = modelDiffer.HasDifferences(
    ((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel(),
    context.Model.GetRelationalModel());

Nous envisageons d’améliorer cette expérience dans 6.0 (voir n° 22031).

Les discriminateurs sont en lecture seule

Suivi de problème n° 21154

Ancien comportement

Il était possible de modifier la valeur du discriminateur avant d’appeler SaveChanges.

Nouveau comportement

Une exception sera levée dans le cas ci-dessus.

Pourquoi

EF ne s’attend pas à ce que le type d’entité change pendant qu’il est encore suivi. La modification de la valeur du discriminateur laisse le contexte dans un état incohérent, ce qui peut entraîner un comportement inattendu.

Corrections

S’il est nécessaire de modifier la valeur du discriminateur et que le contexte sera éliminé immédiatement après l’appel de SaveChanges, le discriminateur peut être rendu mutable :

modelBuilder.Entity<BaseEntity>()
    .Property<string>("Discriminator")
    .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);

Les méthodes EF.Functions spécifiques au fournisseur lèvent une exception pour le fournisseur InMemory

Suivi de problème n° 20294

Ancien comportement

Les méthodes EF.Functions spécifiques au fournisseur contenaient une implémentation pour l’exécution par le client, ce qui leur permettait d’être exécutées sur le fournisseur InMemory. Par exemple, EF.Functions.DateDiffDay est une méthode propre à SQL Server, qui fonctionnait sur le fournisseur InMemory.

Nouveau comportement

Les méthodes spécifiques au fournisseur ont été mises à jour pour lever une exception dans leur corps de méthode afin de bloquer leur évaluation côté client.

Pourquoi

Les méthodes spécifiques au fournisseur sont mappées à une fonction de la base de données. Le calcul effectué par la fonction de base de données mappée ne peut pas toujours être répliqué côté client dans LINQ. Cela peut entraîner des différences dans les résultats obtenus par le serveur lors de l’exécution de la même méthode sur le client. Comme ces méthodes sont utilisées dans LINQ pour traduire des fonctions de base de données spécifiques, elles n’ont pas besoin d’être évaluées côté client. Comme le fournisseur InMemory est une autre base de données, ces méthodes ne sont pas disponibles pour ce fournisseur. Si vous tentez de les exécuter pour le fournisseur InMemory ou tout autre fournisseur qui ne traduit pas ces méthodes, cela lève une exception.

Corrections

Étant donné qu’il n’existe aucun moyen d’imiter avec précision le comportement des fonctions de base de données, vous devez tester les requêtes qui les contiennent avec le même type de base de données qu’en production.

IndexBuilder.HasName est désormais obsolète

Suivi de problème n° 21089

Ancien comportement

Auparavant, un seul index pouvait être défini pour un ensemble donné de propriétés. Le nom de la base de données d’un index était configuré à l’aide d’IndexBuilder.HasName.

Nouveau comportement

Plusieurs index sont désormais autorisés sur le même jeu de propriétés. Ces index sont à présent différenciés par un nom dans le modèle. Par convention, le nom du modèle est utilisé comme nom de la base de données, mais il peut également être configuré indépendamment à l’aide de HasDatabaseName.

Pourquoi

À l’avenir, nous aimerions permettre l’utilisation d’index ascendants et descendants ou d’index avec différents classements sur le même ensemble de propriétés. Ce changement nous permet de faire un pas de plus dans cette direction.

Corrections

Tout code qui appelait auparavant IndexBuilder.HasName doit être mis à jour pour appeler HasDatabaseName à la place.

Si votre projet inclut des migrations générées avant la version 2.0.0 d’EF Core, vous pouvez ignorer l’avertissement dans ces fichiers et le supprimer en ajoutant #pragma warning disable 612, 618.

Un pluraliseur est maintenant inclus pour l’échafaudage de modèles ayant fait l’objet d’une ingénierie à rebours

Suivi de problème n° 11160

Ancien comportement

Auparavant, vous deviez installer un package de pluraliseurs distinct afin de pluraliser les noms de navigation des DbSet et des collections et de singulariser les noms de tables lors de l’échafaudage d’un DbContext et de types d’entités en effectuant l’ingénierie à rebours d’un schéma de base de données.

Nouveau comportement

EF Core inclut désormais un pluraliseur qui utilise la bibliothèque Humanizer. Il s’agit de la même bibliothèque qu’utilise Visual Studio pour recommander des noms de variables.

Pourquoi

L’utilisation du pluriel pour les propriétés de collection et du singulier pour les types et les propriétés de référence est idiomatique en .NET.

Corrections

Pour désactiver le pluraliseur, utilisez l’option --no-pluralize sur dotnet ef dbcontext scaffold ou le commutateur -NoPluralize sur Scaffold-DbContext.

INavigationBase remplace INavigation dans certaines API pour prendre en charge les navigations de redirection

Suivi de problème n° 2568

Ancien comportement

Les versions antérieures à EF Core 5.0 ne prenaient en charge qu’une seule forme de propriété de navigation, représentée par l’interface INavigation.

Nouveau comportement

EF Core 5 0 introduit des relations plusieurs-à-plusieurs qui utilisent des « navigations de redirection ». Celles-ci sont représentées par l’interface ISkipNavigation, et la plupart des fonctionnalités d’INavigation ont été renvoyées à une interface de base commune : INavigationBase.

Pourquoi

La plupart des fonctionnalités des navigations normales et des navigations avec de redirection sont les mêmes. Toutefois, les navigations de redirection ont une relation avec les clés étrangères différente des navigations normales, puisque les clés étrangères concernées ne se trouvent pas directement à l’une ou l’autre extrémité de la relation, mais plutôt dans l’entité de jonction.

Corrections

Dans de nombreux cas, les applications peuvent passer à la nouvelle interface de base sans autre changement. Toutefois, dans les cas où la navigation est utilisée pour accéder aux propriétés des clés étrangères, le code de l’application doit être soit limité aux navigations normales, soit mis à jour pour effectuer l’action appropriée pour les navigations normales et les navigations de redirection.

Certaines requêtes avec une collection corrélée qui utilisent également Distinct ou GroupBy ne sont plus prises en charge

Suivi de problème n° 15873

Ancien comportement

Auparavant, les requêtes impliquant des collections corrélées suivies par GroupBy, ainsi que certaines requêtes utilisant Distinct, étaient autorisées à s’exécuter.

Exemple avec GroupBy :

context.Parents
    .Select(p => p.Children
        .GroupBy(c => c.School)
        .Select(g => g.Key))

Exemple avecDistinct  : requêtes Distinct spécifiques où la projection de la collection interne ne contient pas la clé primaire :

context.Parents
    .Select(p => p.Children
        .Select(c => c.School)
        .Distinct())

Ces requêtes pouvaient retourner des résultats incorrects si la collection interne contenait des doublons, mais fonctionnaient correctement si tous les éléments de la collection interne étaient uniques.

Nouveau comportement

Ces requêtes ne sont plus prises en charge. Une exception est levée et indique que nous ne disposons pas d’assez d’informations pour générer correctement les résultats.

Pourquoi

Pour les scénarios de collecte corrélée, nous devons connaître la clé primaire de l’entité afin d’affecter les entités de la collection au bon parent. Lorsque la collection interne n’utilise pas GroupBy ou Distinct, la clé primaire manquante peut simplement être ajoutée à la projection. Toutefois, si GroupBy et Distinct sont utilisés, cela est impossible, car le résultat de l’opération GroupBy ou Distinct en serait modifié.

Atténuations

Réécrivez la requête de façon à ne pas utiliser les opérations GroupBy ou Distinct sur la collection interne, et effectuez ces opérations sur le client à la place.

context.Parents
    .Select(p => p.Children.Select(c => c.School))
    .ToList()
    .Select(x => x.GroupBy(c => c).Select(g => g.Key))
context.Parents
    .Select(p => p.Children.Select(c => c.School))
    .ToList()
    .Select(x => x.Distinct())

L’utilisation d’une collection de type requêtable dans la projection n’est pas prise en charge

Suivi de problème n° 16314

Ancien comportement

Auparavant, il était parfois possible d’utiliser la collection d’un type requêtable à l’intérieur de la projection, par exemple en tant qu’argument d’un constructeur List<T> :

context.Blogs
    .Select(b => new List<Post>(context.Posts.Where(p => p.BlogId == b.Id)))

Nouveau comportement

Ces requêtes ne sont plus prises en charge. Une exception est levée, indiquant que nous ne pouvons pas créer un objet de type requêtable et suggérant la manière dont cela peut être résolu.

Pourquoi

Nous ne pouvons pas matérialiser un objet d’un type requêtable, de sorte qu’il serait créé automatiquement à l’aide du type List<T> à la place. Cela entraînerait régulièrement une exception en raison d’une incompatibilité de type, ce qui n’était pas très clair et pourrait être surprenant pour certains utilisateurs. Nous avons décidé de reconnaître le modèle et de lever une exception plus significative.

Atténuations

Ajouter un appel ToList() après l’objet requêtable dans la projection :

context.Blogs.Select(b => context.Posts.Where(p => p.BlogId == b.Id).ToList())