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é
Changements à impact moyen
EF Core 5.0 ne prend pas en charge .NET Framework
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
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
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
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
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éthodeSelect
, 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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())