Cambios importantes en EF Core 5.0
Es posible que los siguientes cambios de API y comportamiento puedan interrumpir las aplicaciones actuales cuando se actualicen a EF Core 5.0.0.
Resumen
Cambios de impacto medio
EF Core 5.0 no admite .NET Framework
Problema de seguimiento n.º 15498
Comportamiento anterior
EF Core 3.1 tiene como destino .NET Standard 2.0, que es compatible con .NET Framework.
Comportamiento nuevo
EF Core 5.0 tiene como destino .NET Standard 2.1, que no es compatible con .NET Framework. Esto significa que EF Core 5.0 no se puede usar con aplicaciones de .NET Framework.
Por qué
Esto forma parte de un mayor movimiento entre los equipos de .NET destinado a la unificación en una sola plataforma .NET de destino. Para obtener más información, vea El futuro de .NET Standard.
Mitigaciones
Las aplicaciones de .NET Framework pueden seguir usando EF Core 3.1, que es una versión de soporte a largo plazo (LTS). Como alternativa, es posible actualizar las aplicaciones para que usen .NET Core 3.1 o .NET 5, puesto que ambas plataformas admiten .NET Standard 2.1.
IProperty.GetColumnName() está obsoleto
Incidencia de seguimiento n.º 2266
Comportamiento anterior
GetColumnName()
devolvía el nombre de la columna a la que se asigna una propiedad.
Comportamiento nuevo
GetColumnName()
todavía devuelve el nombre de la columna a la que se asigna una propiedad, pero este comportamiento ahora es ambiguo, ya que EF Core 5 admite el modelo de tabla por tipo y la asignación simultánea a una vista o una función en la que estas asignaciones podrían usar nombres de columna diferentes para la misma propiedad.
Por qué
Hemos marcado este método como obsoleto para guiar a los usuarios a una sobrecarga más precisa: GetColumnName(IProperty, StoreObjectIdentifier).
Mitigaciones
Si el tipo de entidad solo se asigna a una sola tabla y nunca a vistas, funciones o varias tablas, GetColumnBaseName(IReadOnlyProperty) se puede usar en EF Core 5.0 y 6.0 para obtener el nombre de la tabla. Por ejemplo:
var columnName = property.GetColumnBaseName();
En EF Core 7.0, esto se puede volver a reemplazar por el nuevo GetColumnName
, que se comporta como hizo originalmente para asignaciones simples y únicas de tabla.
Si el tipo de entidad se puede asignar a vistas o funciones, o a varias tablas, debe obtenerse StoreObjectIdentifier para identificar la tabla, la vista o la función. Después, se puede usar para obtener el nombre de columna para ese objeto de almacén. Por ejemplo:
var columnName = property.GetColumnName(StoreObjectIdentifier.Table("Users", null)));
Los decimales necesitan precisión y escala
Incidencia de seguimiento n.º 19293
Comportamiento anterior
Normalmente, en EF Core no se establecía la precisión y la escala en los objetos SqlParameter. Esto significa que la precisión y la escala completa se enviaban a SQL Server, donde se redondeaba en función de la precisión y la escala de la columna de base de datos.
Comportamiento nuevo
Ahora en EF Core se establece la precisión y la escala de los parámetros con los valores configurados para las propiedades en el modelo de EF Core. Esto significa que ahora el redondeo se produce en SqlClient. Por tanto, si la precisión y la escala configuradas no coinciden con las de la base de datos, es posible que cambie el redondeo que se ve.
Por qué
Para las características más recientes de SQL Server, incluida Always Encrypted, es necesario que las facetas de parámetro se especifiquen de forma completa. Además, SqlClient ha realizado un cambio para redondear los valores decimales en lugar de truncarlos, lo que coincide con el comportamiento de SQL Server. Esto ha hecho posible que EF Core establezca estas facetas sin cambiar el comportamiento de los decimales configurados correctamente.
Mitigaciones
Asigne las propiedades decimales mediante un nombre de tipo que incluya precisión y escala. Por ejemplo:
public class Blog
{
public int Id { get; set; }
[Column(TypeName = "decimal(16, 5)")]
public decimal Score { get; set; }
}
O bien, use HasPrecision
en las API de creación de modelos. Por ejemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property(e => e.Score).HasPrecision(16, 5);
}
La navegación necesaria o que no acepta valores NULL de una entidad de seguridad a un elemento dependiente tiene una semántica diferente
Incidencia de seguimiento n.º 17286
Comportamiento anterior
Solo las navegaciones a la entidad de seguridad se podían configurar como necesarias. Por lo tanto, el uso de RequiredAttribute
en la navegación a un elemento dependiente (la entidad que contiene la clave externa) o el marcado como que no acepta valores NULL creaba en su lugar la clave externa en el tipo de entidad de definición.
Comportamiento nuevo
Gracias a la compatibilidad agregada con los elementos dependientes necesarios, ahora es posible marcar cualquier navegación de referencia como obligatoria, lo que significa que, en el caso anterior, la clave externa se definirá en el otro lado de la relación y las propiedades no se marcarán como obligatorias.
Llamar a IsRequired
antes de especificar el extremo dependiente ahora es ambiguo:
modelBuilder.Entity<Blog>()
.HasOne(b => b.BlogImage)
.WithOne(i => i.Blog)
.IsRequired()
.HasForeignKey<BlogImage>(b => b.BlogForeignKey);
Por qué
El comportamiento nuevo es obligatorio para habilitar la compatibilidad con los dependientes necesarios (vea n.º 12100).
Mitigaciones
Quite RequiredAttribute
de la navegación al elemento dependiente y colóquelo en su lugar en la navegación de la entidad de seguridad o configure la relación en OnModelCreating
:
modelBuilder.Entity<Blog>()
.HasOne(b => b.BlogImage)
.WithOne(i => i.Blog)
.HasForeignKey<BlogImage>(b => b.BlogForeignKey)
.IsRequired();
La definición de la consulta se reemplaza por métodos específicos del proveedor
Problema de seguimiento n.º 18903
Comportamiento anterior
Los tipos de entidad se asignaban en la definición de consultas en el nivel básico. Cuando el tipo de entidad se usaba en la raíz de la consulta del tipo de entidad se sustituía por la consulta de definición de cualquier proveedor.
Comportamiento nuevo
Las API de definición de consulta han quedado en desuso. Se introdujeron nuevas API específicas del proveedor.
Por qué
Aunque la definición de consulta se implementaba como consulta de reemplazo siempre que se usaba la raíz de la consulta en la consulta, tenía algunos problemas:
- Si la definición de consulta establece la proyección del tipo de entidad con
new { ... }
en el métodoSelect
, su identificación como entidad requería trabajo adicional y lo hacía incoherente con el modo en que EF Core trata los tipos nominales en la consulta. - En el caso de los proveedores relacionales
FromSql
todavía hay que pasar la cadena SQL en forma de expresión LINQ.
Las definiciones de consulta se introdujeron inicialmente como vistas del lado cliente para su uso con el proveedor en memoria para las entidades sin clave (similares a las vistas de base de datos de las bases de datos relacionales). Esta definición facilita la prueba de la aplicación en la base de datos en memoria. Después, se convirtieron en ampliamente aplicables, lo que resultaba útil pero no era coherente y resultaba difícil de entender. Por tanto, decidimos simplificar el concepto. Hemos creado una consulta de definición basada en LINQ exclusiva para el proveedor en memoria, que tratamos de otra forma. Para más información, consulta esta propuesta.
Mitigaciones
En el caso de los proveedores relacionales, use el método ToSqlQuery
en OnModelCreating
y pase una cadena SQL que se utilice como tipo de entidad.
En el caso del proveedor en memoria, use el método ToInMemoryQuery
en OnModelCreating
y pase una consulta LINQ que se utilice como tipo de entidad.
Las consultas no sobrescriben las navegaciones de referencia no nula
Incidencia de seguimiento n.º 2693
Comportamiento anterior
En EF Core 3.1, las instancias de la entidad de la base de datos sobrescribirán las navegaciones de referencia que se inicializan de manera diligente en valores que no sean NULL, con independencia de si coinciden o no los valores de claves. Sin embargo, en otros casos, EF Core 3.1 haría lo contrario y dejaría el valor no NULL existente.
Comportamiento nuevo
A partir de EF Core 5.0, las instancias devueltas por una consulta nunca sobrescriben las navegaciones de referencia no NULL.
Tenga en cuenta que todavía se admite la inicialización diligente de una navegación de colección en una colección vacía.
Por qué
La inicialización de una propiedad de navegación de referencia en una instancia de entidad "vacía" da como resultado un estado ambiguo. Por ejemplo:
public class Blog
{
public int Id { get; set; }
public Author Author { get; set; ) = new Author();
}
Normalmente, una consulta de blogs y autores primero creará instancias Blog
y, después, establecerá las instancias Author
apropiadas en función de los datos devueltos por la base de datos. Sin embargo, en este caso, cada propiedad Blog.Author
ya se ha inicializado en una instancia Author
vacía. Excepto EF Core, no tiene ninguna manera de saber que esta instancia está "vacía". Por lo tanto, si se sobrescribe esta instancia, es posible que se elimine de forma silenciosa una instancia Author
válida. Por lo tanto, EF Core 5.0 ahora no sobrescribe de forma coherente una navegación que ya está inicializada.
Este nuevo comportamiento también se alinea con el comportamiento de EF6 en la mayoría de los casos, aunque en la investigación también encontramos algunos casos de incoherencia en EF6.
Mitigaciones
Si se detecta esta interrupción, la solución consiste en detener la inicialización diligente de las propiedades de navegación de referencia.
ToView() se trata de forma diferente en las migraciones
Incidencia de seguimiento n.º 2725
Comportamiento anterior
Las llamadas a ToView(string)
hacían que las migraciones omitieran el tipo de entidad y lo asignaran a una vista.
Comportamiento nuevo
Ahora ToView(string)
marca el tipo de entidad como no asignado a una tabla y lo asigna a una vista. Como resultado, la primera migración después de actualizar a EF Core 5 intenta quitar la tabla predeterminada para este tipo de entidad, puesto que ya no se omite.
Por qué
EF Core ahora permite asignar un tipo de entidad a una tabla y una vista simultáneamente, de modo que ToView
ya no es un indicador válido que las migraciones deben omitir.
Mitigaciones
Use el siguiente código para marcar la tabla asignada como excluida de las migraciones:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("UserView", t => t.ExcludeFromMigrations());
}
ToTable(null) marca el tipo de entidad como no asignado a una tabla
Incidencia de seguimiento n.º 21172
Comportamiento anterior
ToTable(null)
restablecía el nombre de la tabla al valor predeterminado.
Comportamiento nuevo
Ahora, ToTable(null)
marca el tipo de entidad como no asignado a ninguna tabla.
Por qué
EF Core ahora permite asignar un tipo de entidad a una tabla y una vista simultáneamente, de modo que ToTable(null)
se usa para indicar que no está asignado a ninguna tabla.
Mitigaciones
Use el siguiente código para restablecer el nombre de la tabla al valor predeterminado si no está asignado a una vista o una función DbFunction:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Metadata.RemoveAnnotation(RelationalAnnotationNames.TableName);
}
Cambios de impacto bajo
Se ha quitado el método HasGeometricDimension de la extensión de SQLite NTS
Problema de seguimiento n.º 14257
Comportamiento anterior
Se ha utilizado HasGeometricDimension para habilitar dimensiones adicionales (Z y M) en columnas de geometría. Sin embargo, solo ha afectado a la creación de la base de datos. No era necesario especificarlo para consultar los valores con dimensiones adicionales. Tampoco ha funcionado correctamente al introducir o actualizar valores con dimensiones adicionales (consulte el problema n.º 14257).
Comportamiento nuevo
Para habilitar la inserción y la actualización de valores de geometría con dimensiones adicionales (Z y M), la dimensión debe especificarse como parte del nombre del tipo de columna. Esta API coincide más estrechamente con el comportamiento subyacente de la función AddGeometryColumn de SpatiaLite.
Por qué
El uso de HasGeometricDimension después de especificar la dimensión en el tipo de columna es innecesario y redundante, por lo que se ha quitado HasGeometricDimension por completo.
Mitigaciones
Use HasColumnType
para especificar la dimensión:
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 clave de partición ahora se agrega a la clave principal
Incidencia de seguimiento n.º 15289
Comportamiento anterior
La propiedad de clave de partición solo se agregaba a la clave alternativa que incluye id
.
Comportamiento nuevo
La propiedad de clave de partición ahora también se agrega por convención a la clave principal.
Por qué
Este cambio hace que el modelo se alinee mejor con la semántica de Azure Cosmos DB y mejora el rendimiento de Find
y algunas consultas.
Mitigaciones
Para evitar que la propiedad de clave de partición se agregue a la clave principal, configúrela en OnModelCreating
.
modelBuilder.Entity<Blog>()
.HasKey(b => b.Id);
Azure Cosmos DB: propiedad id
cuyo nombre ha cambiado a __id
Problema de seguimiento n.º 17751
Comportamiento anterior
La propiedad Shadow asignada a la propiedad id
de JSON también se llama id
.
Comportamiento nuevo
La propiedad Shadow creada por convención se denomina ahora __id
.
Por qué
Este cambio hace menos probable que la propiedad id
entre en conflicto con una propiedad existente en el tipo de entidad.
Mitigaciones
Para volver al comportamiento de la versión 3.x, configure la propiedad id
en OnModelCreating
.
modelBuilder.Entity<Blog>()
.Property<string>("id")
.ToJsonProperty("id");
Azure Cosmos DB: byte[] ahora se almacena como cadena Base64 y no como matriz de números
Problema de seguimiento n.º 17306
Comportamiento anterior
Las propiedades de tipo byte[] se almacenaban como matriz de números.
Comportamiento nuevo
Las propiedades de tipo byte[] se almacenan ahora como cadena Base64.
Por qué
Esta representación de byte [] se alinea mejor con las expectativas y es el comportamiento predeterminado de las principales bibliotecas de serialización de JSON.
Mitigaciones
Los datos existentes almacenados como matrices de números se seguirán consultando correctamente, pero actualmente no hay ninguna manera compatible de volver a cambiar el comportamiento de inserción. Si esta limitación bloquea su escenario, haga un comentario en este problema
Azure Cosmos DB: se ha cambiado el nombre de GetPropertyName y SetPropertyName
Problema de seguimiento n.º 17874
Comportamiento anterior
Anteriormente, se llamaba a los métodos de extensión GetPropertyName
y SetPropertyName
Comportamiento nuevo
La API antigua se ha quitado y se han agregado métodos nuevos: GetJsonPropertyName
y SetJsonPropertyName
.
Por qué
Este cambio elimina la ambigüedad en torno a lo que estos métodos configuran.
Mitigaciones
Use la API nueva.
Se llama a los generadores de valores cuando se cambia el estado de la entidad de Desasociado a Sin cambios, Actualizado o Eliminado
Incidencia de seguimiento n.º 15289
Comportamiento anterior
Solo se llamaba a los generadores de valores cuando el estado de la entidad cambiaba a Agregado.
Comportamiento nuevo
Ahora se llama a los generadores de valores cuando se cambia el estado de la entidad de Desasociado a Sin cambios, Actualizado o Eliminado, y la propiedad incluye los valores predeterminados.
Por qué
Este cambio era necesario para mejorar la experiencia con propiedades que no se conservan en el almacén de datos y que su valor se genera siempre en el cliente.
Mitigaciones
Para evitar que se llame al generador de valores, asigne un valor no predeterminado a la propiedad antes de cambiar el estado.
IMigrationsModelDiffer ahora usa IRelationalModel.
Incidencia de seguimiento n.º 20305
Comportamiento anterior
IMigrationsModelDiffer
API se definía mediante IModel
.
Comportamiento nuevo
IMigrationsModelDiffer
API ahora usa IRelationalModel
. Sin embargo, la instantánea del modelo todavía contiene solo IModel
, ya que este código forma parte de la aplicación y Entity Framework no puede cambiarla sin hacer un cambio más importante.
Por qué
IRelationalModel
es una representación recién agregada del esquema de la base de datos. Su uso para encontrar diferencias es más rápido y preciso.
Mitigaciones
Use el código siguiente para comparar el modelo de snapshot
con el modelo 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());
Tenemos previsto mejorar esta experiencia en la versión 6.0 (vea n.º 22031).
Los discriminadores son de solo lectura.
Incidencia de seguimiento n.º 21154
Comportamiento anterior
Era posible cambiar el valor del discriminador antes de llamar a SaveChanges
.
Comportamiento nuevo
En el caso anterior, se producirá una excepción.
Por qué
EF no espera que el tipo de entidad cambie mientras se sigue realizando el seguimiento, por lo que cambiar el valor del discriminador deja el contexto en un estado incoherente, lo que podría dar como resultado un comportamiento inesperado.
Mitigaciones
Si es necesario cambiar el valor del discriminador y el contexto se va a eliminar inmediatamente después de llamar a SaveChanges
, el discriminador se puede convertir en mutable:
modelBuilder.Entity<BaseEntity>()
.Property<string>("Discriminator")
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
Los métodos EF.Functions específicos del proveedor se inician para el proveedor InMemory.
Incidencia de seguimiento n.º 20294
Comportamiento anterior
Los métodos EF.Functions específicos del proveedor incluían la implementación para la ejecución del cliente, lo cual les permitía ejecutarse en el proveedor de InMemory. Por ejemplo, EF.Functions.DateDiffDay
es un método específico de SQL Server que funcionaba en el proveedor InMemory.
Comportamiento nuevo
Los métodos específicos del proveedor se han actualizado para producir una excepción en el cuerpo del método a fin de bloquear su evaluación en el lado cliente.
Por qué
Los métodos específicos del proveedor se asignan a una función de base de datos. El cálculo realizado por la función de base de datos asignada no siempre se puede replicar en el lado cliente en LINQ. Esto puede provocar que el resultado del servidor sea diferente al ejecutar el mismo método en el cliente. Dado que estos métodos se usan en LINQ para traducir a funciones específicas de base de datos, no es necesario que se evalúen en el lado cliente. Dado que el proveedor InMemory es una base de datos diferente, estos métodos no están disponibles para este proveedor. Al intentar ejecutarlos para el proveedor InMemory o cualquier otro proveedor que no traduzca estos métodos, se produce una excepción.
Mitigaciones
Dado que no hay forma de imitar el comportamiento de las funciones de base de datos con precisión, debe probar las consultas que las contienen en el mismo tipo de base de datos que en producción.
IndexBuilder.HasName ahora está obsoleto
Incidencia de seguimiento n.º 21089
Comportamiento anterior
Anteriormente, solo se podía definir un índice en un conjunto determinado de propiedades. El nombre de la base de datos de un índice se configuró mediante IndexBuilder.HasName.
Comportamiento nuevo
Ahora se permiten varios índices en el mismo conjunto o las mismas propiedades. Ahora, estos índices se distinguen por un nombre en el modelo. Por convención, el nombre del modelo se utiliza como nombre de la base de datos; sin embargo, también se puede configurar de forma independiente utilizando HasDatabaseName.
Por qué
En el futuro, nos gustaría habilitar índices ascendentes y descendentes o índices con distintas intercalaciones en el mismo conjunto de propiedades. Este cambio nos hace dar otro paso en esa dirección.
Mitigaciones
Cualquier código que llamara anteriormente a IndexBuilder.HasName debe actualizarse para llamar a HasDatabaseName en su lugar.
Si su proyecto incluye migraciones generadas antes de la versión 2.0.0 de EF Core, puede ignorar la advertencia en esos archivos de forma segura y suprimirla agregando #pragma warning disable 612, 618
.
Ahora se incluye un pluralizador para el scaffolding de los modelos de ingeniería inversa
Incidencia de seguimiento n.º 11160
Comportamiento anterior
Anteriormente, tenía que instalar un paquete de pluralizador independiente para pluralizar los nombres de navegación de colección y DbSet y singularizar los nombres de tabla al aplicar scaffolding a DbContext y los tipos de entidad mediante la utilización de técnicas de ingeniería inversa en un esquema de base de datos.
Comportamiento nuevo
EF Core incluye ahora un pluralizador que usa la biblioteca Humanizer. Se trata de la misma biblioteca que Visual Studio usa para recomendar nombres de variable.
Por qué
El uso de formas plurales de palabras para las propiedades de colección y de formas singulares para los tipos y las propiedades de referencia es idiomático en .NET.
Mitigaciones
Para deshabilitar el pluralizador, use la opción --no-pluralize
en dotnet ef dbcontext scaffold
o el modificador -NoPluralize
en Scaffold-DbContext
.
INavigationBase reemplaza a INavigation en algunas API para admitir la omisión de navegaciones
Incidencia de seguimiento n.º 2568
Comportamiento anterior
Las versiones de EF Core anteriores a la 5.0 solo admiten una forma de propiedad de navegación, representada por la interfaz INavigation
.
Comportamiento nuevo
EF Core 5.0 presenta relaciones varios a varios que usan las "navegaciones por omisión". Se representan mediante la interfaz ISkipNavigation
, y la mayor parte de la funcionalidad de INavigation
se ha insertado en una interfaz base común: INavigationBase
.
Por qué
La mayor parte de la funcionalidad entre las navegaciones normal y por omisión es la misma. Sin embargo, las navegaciones por omisión tienen una relación diferente con las claves externas con respecto a las navegaciones normales, ya que las claves externas implicadas no están directamente en ninguno de los extremos de la relación, sino en la entidad join.
Mitigaciones
En muchos casos, las aplicaciones pueden cambiar al uso de la nueva interfaz base sin realizar ningún otro cambio. Sin embargo, en los casos en los que se usa la navegación para acceder a las propiedades de clave externa, el código de aplicación se debe restringir solo a las navegaciones normales, o bien actualizarse para hacer lo adecuado para las navegaciones normal y por omisión.
Ya no se admiten algunas consultas con colecciones correlacionadas que también usan Distinct
o GroupBy
Incidencia de seguimiento n.º 15873
Comportamiento anterior
Anteriormente, las consultas que implicaban colecciones correlacionadas seguidas de GroupBy
, así como algunas consultas con Distinct
que permitimos ejecutar.
Ejemplo de GroupBy:
context.Parents
.Select(p => p.Children
.GroupBy(c => c.School)
.Select(g => g.Key))
Ejemplo de Distinct
: en concreto, las consultas Distinct
en las que la proyección de la colección interna no contiene la clave principal:
context.Parents
.Select(p => p.Children
.Select(c => c.School)
.Distinct())
Estas consultas podrían devolver resultados incorrectos si la colección interna contuviera duplicados, pero funcionaría correctamente si todos los elementos de la colección interna fueran únicos.
Comportamiento nuevo
Estas consultas ya no son compatibles. Se produce una excepción que indica que no hay suficiente información para compilar los resultados correctamente.
Por qué
En el caso de los escenarios de colecciones correlacionadas, es necesario conocer la clave principal de la entidad para asignar entidades de colección al elemento primario correcto. Cuando la colección interna no utiliza GroupBy
ni Distinct
, la clave principal que falta se puede agregar simplemente a la proyección. Sin embargo, en el caso de GroupBy
y Distinct
, no se puede hacer porque cambiaría el resultado de la operación GroupBy
o Distinct
.
Mitigaciones
Vuelva a escribir la consulta para que no use las operaciones GroupBy
o Distinct
en la colección interna y, en su lugar, realice estas operaciones en el cliente.
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())
No se admite el uso de una colección de tipo consultable en la proyección
Incidencia de seguimiento n.º 16314
Comportamiento anterior
Anteriormente, era posible usar la colección de un tipo consultable dentro de la proyección en algunos casos, por ejemplo, como un argumento de un constructor List<T>
:
context.Blogs
.Select(b => new List<Post>(context.Posts.Where(p => p.BlogId == b.Id)))
Comportamiento nuevo
Estas consultas ya no son compatibles. Se produce una excepción que indica que no se puede crear un objeto de tipo consultable y, además, se sugiere cómo corregir este problema.
Por qué
No se puede materializar un objeto de un tipo consultable, por lo que, en su lugar, se creará automáticamente con el tipo List<T>
. Esto suele provocar una excepción debido a una falta de coincidencia de tipos que no era muy clara y podría ser sorprendente para algunos usuarios. Decidimos reconocer el patrón y producir una excepción más significativa.
Mitigaciones
Agregue la llamada a ToList()
después del objeto consultable en la proyección:
context.Blogs.Select(b => context.Posts.Where(p => p.BlogId == b.Id).ToList())