Compartir a través de


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

Cambio importante Impacto
EF Core 5.0 no admite .NET Framework Media
IProperty.GetColumnName() está obsoleto Media
Los decimales necesitan precisión y escala Media
La navegación necesaria o que no acepta valores NULL de una entidad de seguridad a un elemento dependiente tiene una semántica diferente Media
La definición de la consulta se reemplaza por métodos específicos del proveedor Media
Las consultas no sobrescriben las navegaciones de referencia no nula Media
ToView() se trata de forma diferente en las migraciones Media
ToTable(null) marca el tipo de entidad como no asignado a una tabla Media
Se ha quitado el método HasGeometricDimension de la extensión de SQLite NTS Bajo
Azure Cosmos DB: La clave de partición ahora se agrega a la clave principal Bajo
Azure Cosmos DB: el nombre de la propiedad id ha cambiado a __id Bajo
Azure Cosmos DB: byte[] ahora se almacena como cadena Base64 y no como matriz de números Bajo
Azure Cosmos DB: se ha cambiado el nombre de GetPropertyName y SetPropertyName Bajo
Se llama a los generadores de valores cuando se cambia el estado de la entidad de Desasociado a Sin cambios, Actualizado o Eliminado Bajo
IMigrationsModelDiffer ahora usa IRelationalModel. Bajo
Los discriminadores son de solo lectura. Bajo
Los métodos EF.Functions específicos del proveedor se inician para el proveedor InMemory Bajo
IndexBuilder.HasName ahora está obsoleto Bajo
Ahora se incluye un pluralizador para el scaffolding de los modelos de ingeniería inversa Bajo
INavigationBase reemplaza a INavigation en algunas API para admitir la omisión de navegaciones Bajo
Ya no se admiten algunas consultas con colecciones correlacionadas que también usan Distinct o GroupBy Bajo
No se admite el uso de una colección de tipo consultable en la proyección Bajo

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étodo Select, 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())