Cambios importantes en EF Core 6.0

Las siguientes API y cambios de comportamiento puedan interrumpir las aplicaciones existentes cuando se actualicen a EF Core 6.0.

Marco de destino

EF Core 6.0 tiene como destino .NET 6. Las aplicaciones destinadas a versiones anteriores de .NET, .NET Core y .NET Framework tendrán que tener como destino .NET 6 para usar EF Core 6.0.

Resumen

Cambio importante Impacto
Los dependientes opcionales anidados que comparten una tabla y sin propiedades necesarias no se pueden guardar Alto
Cambiar el propietario de una entidad en propiedad ahora inicia una excepción Media
Azure Cosmos DB: Los tipos de entidad relacionados se detectan como en propiedad Media
SQLite: las conexiones se agrupan Media
Las relaciones de varios a varios sin entidades de combinación asignadas ahora están con scaffolding Media
Se ha limpiado la asignación entre los valores DeleteBehavior y ON DELETE Bajo
La base de datos en memoria valida que las propiedades necesarias no contengan valores NULL Bajo
Se ha quitado la última cláusula ORDER BY al realizar la combinación en colecciones Bajo
DbSet ya no implementa IAsyncEnumerable Bajo
El tipo de entidad devuelta de TVF también se asigna a una tabla de manera predeterminada Bajo
La exclusividad del nombre de la restricción CHECK ahora está validada Bajo
Se han agregado interfaces de metadatos de IReadOnly y se han quitado métodos de extensión Bajo
IExecutionStrategy es ahora un servicio singleton Bajo
SQL Server: se consideran transitorios más errores Bajo
Azure Cosmos DB: Se escapan más caracteres en valores 'id' Bajo
Algunos servicios singleton ahora tienen ámbito Bajo*
Nueva API de almacenamiento en caché para extensiones que agregan o reemplazan servicios Bajo*
Nuevo procedimiento de inicialización de modelos en tiempo de diseño e instantáneas Bajo
OwnedNavigationBuilder.HasIndex ahora devuelve un tipo diferente Bajo
DbFunctionBuilder.HasSchema(null) invalida [DbFunction(Schema = "schema")] Bajo
Las navegaciones inicializadas previamente se reemplazan por valores de las consultas de base de datos Bajo
Los valores de cadena de enumeración desconocidos de la base de datos no se convierten al valor predeterminado de enumeración cuando se consulta Bajo
DbFunctionBuilder.HasTranslation ahora proporciona los argumentos de función como IReadOnlyList en lugar de IReadOnlyCollection Bajo
La asignación de tabla predeterminada no se quita cuando la entidad se asigna a una función con valores de tabla Bajo
dotnet-ef tiene como destino .NET 6 Bajo
Es posible que sea necesario actualizar las implementaciones IModelCacheKeyFactory para controlar el almacenamiento en caché en tiempo de diseño Bajo
NavigationBaseIncludeIgnored ahora es un error de forma predeterminada Bajo

* Estos cambios son de especial interés para los autores de extensiones y proveedores de bases de datos.

Cambios de impacto alto

Los dependientes opcionales anidados que comparten una tabla y sin propiedades necesarias no están permitidos

Incidencia de seguimiento n.º 24558

Comportamiento anterior

Se permitían los modelos con dependientes opcionales anidados que comparten una tabla y sin propiedades necesarias, pero podían provocar una pérdida de datos al consultar los datos y, posteriormente, volver a guardarlos. Por ejemplo, tomemos el siguiente modelo:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ContactInfo ContactInfo { get; set; }
}

[Owned]
public class ContactInfo
{
    public string Phone { get; set; }
    public Address Address { get; set; }
}

[Owned]
public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

No se requiere ninguna de las propiedades de ContactInfo o Address, y todos estos tipos de entidad están asignados a la misma tabla. Las reglas para los dependientes opcionales (en lugar de los dependientes necesarios) dicen que si todas las columnas de ContactInfo son NULL, no se creará ninguna instancia de ContactInfo al consultar el propietario Customer. Sin embargo, esto también significa que no se creará ninguna instancia de Address, incluso si las columnas Address no son NULL.

Comportamiento nuevo

Intentar usar este modelo generará la siguiente excepción:

System.InvalidOperationException: el tipo de entidad "ContactInfo" es un dependiente opcional que utiliza el uso compartido de tablas y que contiene otros dependientes sin ninguna propiedad no compartida necesaria para identificar si la entidad existe. Si todas las propiedades que aceptan valores NULL contienen un valor NULL en la base de datos, no se creará una instancia de objeto en la consulta, lo que provocará la pérdida de los valores del dependiente anidado. Agregue una propiedad necesaria para crear instancias con valores NULL para otras propiedades o marque la navegación entrante como necesaria para que siempre se cree una instancia.

Esto evita la pérdida de datos al consultar y guardar datos.

Por qué

El uso de modelos con dependientes opcionales anidados que comparten una tabla y sin propiedades necesarias a menudo producía una pérdida de datos silenciosa.

Mitigaciones

Evite el uso de dependientes opcionales que comparten una tabla y sin propiedades necesarias. Hay tres maneras sencillas de hacerlo:

  1. Haga que los dependientes sean necesarios. Esto significa que la entidad dependiente siempre tendrá un valor después de consultarla, incluso si todas sus propiedades son NULL. Por ejemplo:

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        [Required]
        public Address Address { get; set; }
    }
    

    O:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.OwnsOne(e => e.Address);
                b.Navigation(e => e.Address).IsRequired();
            });
    
  2. Asegúrese de que el dependiente contiene al menos una propiedad necesaria.

  3. Asigne los dependientes opcionales en su propia tabla, en lugar de compartir una tabla con la entidad de seguridad. Por ejemplo:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.ToTable("Customers");
                b.OwnsOne(e => e.Address, b => b.ToTable("CustomerAddresses"));
            });
    

En la documentación de Novedades de EF Core 6.0 se incluyen los problemas relacionados con los dependientes opcionales y ejemplos de estas mitigaciones.

Cambios de impacto medio

Cambiar el propietario de una entidad en propiedad ahora inicia una excepción

Incidencia de seguimiento n.º 4073

Comportamiento anterior

Era posible reasignar una entidad en propiedad a otra entidad del propietario.

Comportamiento nuevo

Esta acción ahora iniciará una excepción:

La propiedad "{entityType}.{property}" forma parte de una clave y, por tanto, no se puede modificar ni marcar como modificada. Para cambiar la entidad de seguridad de una entidad existente con una clave externa de identificación, elimine primero el elemento dependiente e invoque "SaveChanges". A continuación, asocie el elemento dependiente con la nueva entidad de seguridad.

Por qué

Aunque no es necesario que existan propiedades de clave en un tipo en propiedad, EF creará propiedades reemplazadas que se usarán como clave principal y la clave externa que apunta al propietario. Cuando se cambia la entidad de propietario, los valores de la clave externa de la entidad en propiedad cambian y, puesto que también se usan como clave principal, cambia la identidad de la entidad. Todavía no es totalmente compatible con EF Core y solo se permitía condicionalmente para las entidades en propiedad, lo que a veces provocaba que el estado interno se volviera incoherente.

Mitigaciones

En lugar de asignar la misma instancia en propiedad a un propietario nuevo, puede asignar una copia y eliminar la anterior.

Incidencia de seguimiento n.º 24803Novedades: El valor predeterminado es la propiedad implícita

Comportamiento anterior

Al igual que en otros proveedores, los tipos de entidad relacionados se detectaban como tipos normales (no en propiedad).

Comportamiento nuevo

Los tipos de entidad relacionados ahora serán propiedad del tipo de entidad en el que se han detectado. Solo los tipos de entidad que corresponden a una propiedad DbSet<TEntity> se detectarán como no en propiedad.

Por qué

Este comportamiento sigue el patrón común de modelado de datos en Azure Cosmos DB de inserción de datos relacionados en un único documento. Azure Cosmos DB no admite la unión de documentos diferentes de forma nativa, por lo que el modelado de entidades relacionadas como no en propiedad tiene una utilidad limitada.

Mitigaciones

Para configurar un tipo de entidad como no en propiedad, llame a modelBuilder.Entity<MyEntity>();

SQLite: las conexiones se agrupan

Incidencia de seguimiento n.º 13837Novedades: el valor predeterminado es la propiedad implícita

Comportamiento anterior

Anteriormente, las conexiones de Microsoft.Data.Sqlite no se agrupaban.

Comportamiento nuevo

A partir de la versión 6.0, las conexiones se agrupan de forma predeterminada. Como resultado, el proceso mantiene abiertos los archivos de base de datos incluso después de cerrar el objeto de conexión ADO.NET.

Por qué

La agrupación de las conexiones subyacentes mejora considerablemente el rendimiento de abrir y cerrar objetos de conexión ADO.NET. Esto es especialmente visible en escenarios en los que la apertura de la conexión subyacente es costosa, como en el caso del cifrado, o en escenarios en los que hay una gran cantidad de conexiones de corta duración a la base de datos.

Mitigaciones

La agrupación de conexiones se puede deshabilitar agregando Pooling=False a una cadena de conexión.

En algunos escenarios (como la eliminación del archivo de base de datos), pueden producirse errores que indican que el archivo todavía está en uso. Puede borrar manualmente el grupo de conexiones antes de realizar las operaciones del archivo mediante SqliteConnection.ClearPool().

SqliteConnection.ClearPool(connection);
File.Delete(databaseFile);

Las relaciones de varios a varios sin entidades de combinación asignadas ahora están con scaffolding

Incidencia de seguimiento n.º 22475

Comportamiento anterior

El scaffolding (utilización de técnicas de ingeniería inversa) de DbContext y los tipos de entidad de una base de datos existente siempre asignaba explícitamente tablas de combinación para unir tipos de entidad en las relaciones de varios a varios.

Comportamiento nuevo

Las tablas de combinación simples que contienen solo dos propiedades de clave externa a otras tablas ya no se asignan a tipos de entidad explícitos, sino que se asignan como una relación de varios a varios entre las dos tablas combinadas.

Por qué

Las relaciones de varios a varios sin tipos de combinación explícitos se introdujeron en EF Core 5.0 y son una manera más limpia y natural de representar tablas de combinación simples.

Mitigaciones

Hay dos mitigaciones. El enfoque preferido es actualizar el código para usar directamente las relaciones de varios a varios. Es muy poco frecuente que el tipo de entidad de combinación deba usarse directamente cuando solo contiene dos claves externas para las relaciones de varios a varios.

Como alternativa, la entidad de combinación explícita se puede volver a agregar al modelo de EF. Por ejemplo, suponiendo una relación de varios a varios entre Post y Tag, vuelva a agregar el tipo de combinación y las navegaciones mediante clases parciales:

public partial class PostTag
{
    public int PostsId { get; set; }
    public int TagsId { get; set; }

    public virtual Post Posts { get; set; }
    public virtual Tag Tags { get; set; }
}

public partial class Post
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

public partial class Tag
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

A continuación, agregue la configuración para el tipo de combinación y las navegaciones a una clase parcial para DbContext:

public partial class DailyContext
{
    partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>(entity =>
        {
            entity.HasMany(d => d.Tags)
                .WithMany(p => p.Posts)
                .UsingEntity<PostTag>(
                    l => l.HasOne<Tag>(e => e.Tags).WithMany(e => e.PostTags).HasForeignKey(e => e.TagsId),
                    r => r.HasOne<Post>(e => e.Posts).WithMany(e => e.PostTags).HasForeignKey(e => e.PostsId),
                    j =>
                    {
                        j.HasKey("PostsId", "TagsId");
                        j.ToTable("PostTag");
                    });
        });
    }
}

Por último, quite la configuración generada para la relación de varios a varios del contexto con scaffolding. Esto es necesario porque el tipo de entidad de combinación con scaffolding debe quitarse del modelo para poder usar el tipo explícito. Este código tendrá que quitarse cada vez que se aplique scaffolding al contexto, pero se conservará porque el código anterior está en clases parciales.

Tenga en cuenta que, con esta configuración, la entidad de combinación se puede usar explícitamente, al igual que en las versiones anteriores de EF Core. Sin embargo, la relación también se puede usar como una relación de varios a varios. Esto significa que actualizar el código de esta forma puede ser una solución temporal mientras se actualiza el resto del código para usar la relación como una relación de varios a varios de forma natural.

Cambios de impacto bajo

Se ha limpiado la asignación entre los valores DeleteBehavior y ON DELETE

Incidencia de seguimiento n.º 21252

Comportamiento anterior

Algunas de las asignaciones entre el comportamiento de OnDelete() de una relación y el comportamiento ON DELETE de las claves externas en la base de datos eran incoherentes en migraciones y scaffolding.

Comportamiento nuevo

En la tabla siguiente se muestran los cambios para Migraciones.

OnDelete() ON DELETE
NoAction NO ACTION
ClientNoAction NO ACTION
Restringir RESTRICT
Cascasde CASCADE
ClientCascade RESTRICTNO ACTION
SetNull SET NULL
ClientSetNull RESTRICTNO ACTION

Los cambios de scaffolding son los siguientes.

ON DELETE OnDelete()
NO ACTION ClientSetNull
RESTRICT ClientSetNullRestrict
CASCADE Cascada
SET NULL SetNull

Por qué

Las nuevas asignaciones son más coherentes. Ahora se prefiere el comportamiento predeterminado de la base de datos de NO ACTION al comportamiento RESTRICT más restrictivo y de menor rendimiento.

Mitigaciones

El comportamiento predeterminado de OnDelete() de las relaciones opcionales es ClientSetNull. Su asignación ha cambiado de RESTRICT a NO ACTION. Esto puede provocar que se generen muchas operaciones en la primera migración agregada después de actualizar a EF Core 6.0.

Puede optar por aplicar estas operaciones o quitarlas manualmente de la migración, ya que no tienen ningún impacto funcional en EF Core.

SQL Server no admite RESTRICT, por lo que estas claves externas ya se han creado con NO ACTION. Las operaciones de migración no tendrán ningún efecto en SQL Server y se pueden quitar con seguridad.

La base de datos en memoria valida que las propiedades necesarias no contengan valores NULL

Incidencia de seguimiento n.º 10613

Comportamiento anterior

La base de datos en memoria permitía guardar valores NULL incluso cuando la propiedad se configuraba como necesaria.

Comportamiento nuevo

La base de datos en memoria produce una excepción Microsoft.EntityFrameworkCore.DbUpdateException cuando se llama a SaveChanges o SaveChangesAsync y se establece una propiedad necesaria en NULL.

Por qué

El comportamiento de la base de datos en memoria ahora coincide con el comportamiento de otras bases de datos.

Mitigaciones

El comportamiento anterior (es decir, no comprobar la existencia de valores NULL) se puede restaurar al configurar el proveedor en memoria. Por ejemplo:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseInMemoryDatabase("MyDatabase", b => b.EnableNullChecks(false));
}

Se ha quitado la última cláusula ORDER BY al realizar la combinación en colecciones

Incidencia de seguimiento n.º 19828

Comportamiento anterior

Al realizar operaciones JOIN de SQL en colecciones (relaciones uno a varios), EF Core agregaba una cláusula ORDER BY para cada columna de clave de la tabla unida. Por ejemplo, la carga de todos los blogs con sus publicaciones relacionadas se realizaba mediante el código SQL siguiente:

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]

Estas ordenaciones son necesarias para la materialización adecuada de las entidades.

Comportamiento nuevo

Ahora se omite la última cláusula ORDER BY para una combinación de colección:

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]

Ya no se genera una cláusula ORDER BY para la columna ID de la publicación.

Por qué

Cada cláusula ORDER BY impone trabajo adicional en la base de datos y la última ordenación no es necesaria para las necesidades de materialización de EF Core. Los datos muestran que la eliminación de esta última ordenación puede producir una mejora significativa del rendimiento en algunos escenarios.

Mitigaciones

Si la aplicación espera que se devuelvan entidades combinadas en un orden concreto, haga que sea explícito y agregue un operador OrderBy de LINQ a la consulta.

DbSet ya no implementa IAsyncEnumerable

Incidencia de seguimiento n.º 24041

Comportamiento anterior

DbSet<TEntity>, que se usa para ejecutar consultas en DbContext, solía implementar IAsyncEnumerable<T>.

Comportamiento nuevo

DbSet<TEntity> ya no implementa directamente IAsyncEnumerable<T>.

Por qué

DbSet<TEntity> originalmente se hizo para implementar IAsyncEnumerable<T> principalmente con el fin de permitir la enumeración directa mediante la construcción foreach. Desafortunadamente, cuando un proyecto también hace referencia a System.Linq.Async para crear operadores LINQ asincrónicos en el lado cliente, producía un error de invocación ambiguo entre los operadores definidos sobre IQueryable<T> y los definidos sobre IAsyncEnumerable<T>. C# 9 agregó compatibilidad con la extensión GetEnumerator para bucles foreach, con lo que se eliminó el motivo principal original para hacer referencia a IAsyncEnumerable.

La gran mayoría de las utilizaciones de DbSet seguirán funcionando tal y como están, ya que componen operadores LINQ sobre DbSet, los enumeran, etc. Las únicas utilizaciones interrumpidas son las que intentan convertir directamente DbSet a IAsyncEnumerable.

Mitigaciones

Si necesita hacer referencia a DbSet<TEntity> como IAsyncEnumerable<T>, llame a DbSet<TEntity>.AsAsyncEnumerable para convertirla explícitamente.

El tipo de entidad devuelta de TVF también se asigna a una tabla de manera predeterminada

Incidencia de seguimiento n.º 23408

Comportamiento anterior

Un tipo de entidad no se asignaba a una tabla de manera predeterminada cuando se usaba como tipo de valor devuelto de una TVF configurada con HasDbFunction.

Comportamiento nuevo

Un tipo de entidad utilizado como tipo de valor devuelto de una TVF conserva la asignación de tabla predeterminada.

Por qué

No es intuitivo que, al configurar una TVF, se quite la asignación de tabla predeterminada del tipo de entidad devuelta.

Mitigaciones

Para quitar la asignación de tabla predeterminada, llame a ToTable(EntityTypeBuilder, String):

modelBuilder.Entity<MyEntity>().ToTable((string?)null));

La exclusividad del nombre de la restricción CHECK ahora está validada

Incidencia de seguimiento n.º 25061

Comportamiento anterior

Se permitía declarar y usar restricciones CHECK con el mismo nombre en la misma tabla.

Comportamiento nuevo

Al configurar explícitamente dos restricciones CHECK con el mismo nombre en la misma tabla, se producirá una excepción. Se asignará un nombre único a las restricciones CHECK creadas por una convención.

Por qué

La mayoría de las bases de datos no permiten crear dos restricciones CHECK con el mismo nombre en la misma tabla, y algunas requieren que el nombre sea único incluso entre tablas. Provocaría que se produjera una excepción al aplicar una migración.

Mitigaciones

En algunos casos, los nombres de restricción CHECK válidos pueden ser diferentes debido a este cambio. Para especificar el nombre deseado explícitamente, llame a HasName:

modelBuilder.Entity<MyEntity>().HasCheckConstraint("CK_Id", "Id > 0", c => c.HasName("CK_MyEntity_Id"));

Se han agregado interfaces de metadatos de IReadOnly y se han quitado métodos de extensión

Incidencia de seguimiento n.º 19213

Comportamiento anterior

Había tres conjuntos de interfaces de metadatos: IModel, IMutableModel y IConventionModel, así como métodos de extensión.

Comportamiento nuevo

Se ha agregado un nuevo conjunto de interfaces IReadOnly, p. ej., IReadOnlyModel. Los métodos de extensión que se definían para las interfaces de metadatos se han convertido en métodos de interfaz predeterminados.

Por qué

Los métodos de interfaz predeterminados permiten invalidar la implementación, lo que aprovecha la nueva implementación del modelo en tiempo de ejecución para ofrecer un mejor rendimiento.

Mitigaciones

Estos cambios no deberían afectar a la mayoría del código. Sin embargo, si usara los métodos de extensión a través de la sintaxis de invocación estática, tendría que convertirse a la sintaxis de invocación de la instancia.

IExecutionStrategy es ahora un servicio singleton

Incidencia de seguimiento n.º 21350

Comportamiento nuevo

IExecutionStrategy ahora es un servicio singleton. Esto significa que cualquier estado agregado en las implementaciones personalizadas se conservará entre ejecuciones y el delegado pasado a ExecutionStrategy solo se ejecutará una vez.

Por qué

Esto reduce las asignaciones en dos rutas de acceso activas en EF.

Mitigaciones

Las implementaciones que derivan de ExecutionStrategy deben borrar cualquier estado en OnFirstExecution().

La lógica condicional del delegado pasado a ExecutionStrategy se debe mover a una implementación personalizada de IExecutionStrategy.

SQL Server: se consideran transitorios más errores

Incidencia de seguimiento n.º 25050

Comportamiento nuevo

Los errores enumerados en la incidencia anterior ahora se consideran transitorios. Cuando use la estrategia de ejecución predeterminada (sin reintentos), estos errores se encapsularán en una instancia de excepción adicional.

Por qué

Seguimos recopilando comentarios de los usuarios y el equipo de SQL Server sobre los errores deben considerarse transitorios.

Mitigaciones

Para cambiar el conjunto de errores que se consideran transitorios, use una estrategia de ejecución personalizada que pueda derivarse de SqlServerRetryingExecutionStrategy - Resistencia de conexión - EF Core.

Azure Cosmos DB: Se escapan más caracteres en valores 'id'

Incidencia de seguimiento n.º 25100

Comportamiento anterior

En EF Core 5, solo '|' se escapaba en valores id.

Comportamiento nuevo

En EF Core 6, '/', '\', '?' y '#' también se escapan en valores id.

Por qué

Estos caracteres no son válidos, como se documenta en Resource.Id. Si se usan id en, se producirá un error en las consultas.

Mitigaciones

Puede invalidar el valor generado si lo establece antes de que la entidad se marque como Added:

var entry = context.Attach(entity);
entry.Property("__id").CurrentValue = "MyEntity|/\\?#";
entry.State = EntityState.Added;

Algunos servicios singleton ahora tienen ámbito

Incidencia de seguimiento n.º 25084

Comportamiento nuevo

Muchos servicios de consulta y algunos servicios en tiempo de diseño que se registraban como Singleton ahora se registran como Scoped.

Por qué

Se ha tenido que cambiar la duración para permitir que una nueva característica, DefaultTypeMapping, afecte a las consultas.

Las duraciones de los servicios en tiempo de diseño se han ajustado para que coincidan con las duraciones de los servicios en tiempo de ejecución a fin de evitar errores al usar ambos.

Mitigaciones

Use TryAdd para registrar los servicios EF Core con la duración predeterminada. Use solo TryAddProviderSpecificServices para los servicios no agregados por EF.

Nueva API de almacenamiento en caché para extensiones que agregan o reemplazan servicios

Incidencia de seguimiento n.º 19152

Comportamiento anterior

En EF Core 5, GetServiceProviderHashCode devolvía long y se usaba directamente como parte de la clave de caché del proveedor de servicios.

Comportamiento nuevo

GetServiceProviderHashCode ahora devuelve int y solo se usa para calcular el código hash de la clave de caché del proveedor de servicios.

Además, debe implementarse ShouldUseSameServiceProvider para indicar si el objeto actual representa la misma configuración de servicio y, por tanto, puede usar el mismo proveedor de servicios.

Por qué

El uso de un código hash como parte de la clave de caché daba lugar a colisiones ocasionales que eran difíciles de diagnosticar y corregir. El método adicional garantiza que solo se usa el mismo proveedor de servicios cuando sea pertinente.

Mitigaciones

Muchas extensiones no exponen ninguna opción que afecte a los servicios registrados y pueden usar la implementación de ShouldUseSameServiceProvider siguiente:

private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
    public ExtensionInfo(IDbContextOptionsExtension extension)
        : base(extension)
    {
    }

    ...

    public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
        => other is ExtensionInfo;
}

De lo contrario, se deben agregar más predicados para comparar todas las opciones pertinentes.

Nuevo procedimiento de inicialización de modelos en tiempo de diseño e instantáneas

Incidencia de seguimiento n.º 22031

Comportamiento anterior

En EF Core 5, era necesario invocar convenciones específicas antes de que el modelo de instantáneas estuviera listo para su uso.

Comportamiento nuevo

Se han incorporado IModelRuntimeInitializer para ocultar algunos de los pasos necesarios y un modelo en tiempo de ejecución que no tiene todos los metadatos de las migraciones, por lo que el modelo en tiempo de diseño debe usarse para la diferenciación de modelos.

Por qué

IModelRuntimeInitializer abstrae los pasos de finalización del modelo, por lo que ahora se pueden modificar sin más cambios importantes para los usuarios.

Se ha incorporado el modelo optimizado en tiempo de ejecución para mejorar el rendimiento en tiempo de ejecución. Tiene varias optimizaciones, una de las cuales es quitar metadatos que no se usan en tiempo de ejecución.

Mitigaciones

En el fragmento de código siguiente se muestra cómo comprobar si el modelo actual es diferente del modelo de instantáneas:

var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;

if (snapshotModel is IMutableModel mutableModel)
{
    snapshotModel = mutableModel.FinalizeModel();
}

if (snapshotModel != null)
{
    snapshotModel = context.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
}

var hasDifferences = context.GetService<IMigrationsModelDiffer>().HasDifferences(
    snapshotModel?.GetRelationalModel(),
    context.GetService<IDesignTimeModel>().Model.GetRelationalModel());

En este fragmento de código se muestra cómo implementar IDesignTimeDbContextFactory<TContext> mediante la creación de un modelo externamente y la llamada a UseModel:

internal class MyDesignContext : IDesignTimeDbContextFactory<MyContext>
{
    public TestContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder();
        optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DB"));

        var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder();
        CustomizeModel(modelBuilder);
        var model = modelBuilder.Model.FinalizeModel();

        var serviceContext = new MyContext(optionsBuilder.Options);
        model = serviceContext.GetService<IModelRuntimeInitializer>().Initialize(model);
        return new MyContext(optionsBuilder.Options);
    }
}

OwnedNavigationBuilder.HasIndex ahora devuelve un tipo diferente

Incidencia de seguimiento n.º 24005

Comportamiento anterior

En EF Core 5, HasIndex devolvía IndexBuilder<TEntity>, donde TEntity es el tipo de propietario.

Comportamiento nuevo

HasIndex ahora devuelve IndexBuilder<TDependentEntity>, donde TDependentEntity es el tipo en propiedad.

Por qué

El objeto de generador devuelto no se especificaba correctamente.

Mitigaciones

Recompilar el ensamblado con la versión más reciente de EF Core será suficiente para corregir los problemas causados por este cambio.

DbFunctionBuilder.HasSchema(null) invalida [DbFunction(Schema = "schema")]

Incidencia de seguimiento n.º 24228

Comportamiento anterior

En EF Core 5, la llamada a HasSchema con el valor null no almacenaba el origen de configuración, por lo que DbFunctionAttribute pudo invalidarlo.

Comportamiento nuevo

La llamada a HasSchema con el valor null ahora almacena el origen de configuración e impide que el atributo lo invalide.

Por qué

Las anotaciones de datos no deben poder invalidar la configuración especificada con la API ModelBuilder.

Mitigaciones

Quite la llamada a HasSchema para permitir que el atributo configure el esquema.

Las navegaciones inicializadas previamente se reemplazan por valores de las consultas de base de datos

Incidencia de seguimiento n.º 23851

Comportamiento anterior

Las propiedades de navegación establecidas en un objeto vacío no se modificaban para las consultas de seguimiento, pero se sobrescribían para las consultas que no eran de seguimiento. Por ejemplo, considere los tipos de entidad siguientes:

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

    public Bar Bar { get; set; } = new(); // Don't do this.
}

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

Una consulta sin seguimiento de Foo que incluya Bar establecido en Foo.Bar a la entidad consultada desde la base de datos. Por ejemplo, este código:

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Imprimía Foo.Bar.Id = 1.

Sin embargo, la misma consulta ejecutada para el seguimiento no sobrescribía Foo.Bar con la entidad consultada desde la base de datos. Por ejemplo, este código:

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Imprimía Foo.Bar.Id = 0.

Comportamiento nuevo

En EF Core 6.0, el comportamiento de las consultas de seguimiento ahora coincide con el de las consultas sin seguimiento. Esto significa que este código:

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Y este código:

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Imprimen Foo.Bar.Id = 1.

Por qué

Hay dos razones para realizar este cambio:

  1. Asegurarse de que las consultas de seguimiento y sin seguimiento tienen un comportamiento coherente.
  2. Cuando se consulta una base de datos, es razonable suponer que el código de la aplicación quiere recuperar los valores almacenados en la base de datos.

Mitigaciones

Hay dos mitigaciones:

  1. No consulte los objetos de la base de datos que no deban incluirse en los resultados. Por ejemplo, en los fragmentos de código anteriores, no IncludeFoo.Bar si la instancia Bar no debe devolverse de la base de datos e incluirse en los resultados.
  2. Establezca el valor de la navegación después de realizar consultas desde la base de datos. Por ejemplo, en los fragmentos de código anteriores, llame a foo.Bar = new() después de ejecutar la consulta.

Además, considere la posibilidad de no inicializar instancias de entidad relacionadas en objetos predeterminados. Esto implica que la instancia relacionada es una nueva entidad, no guardada en la base de datos, sin ningún valor de clave establecido. Si, en su lugar, la entidad relacionada existe en la base de datos, los datos del código no tienen probabilidades con los datos almacenados en la base de datos.

Los valores de cadena de enumeración desconocidos de la base de datos no se convierten al valor predeterminado de enumeración cuando se consulta

Incidencia de seguimiento n.º 24084

Comportamiento anterior

Las propiedades de enumeración se pueden asignar a columnas de cadena en la base de datos mediante HasConversion<string>() o EnumToStringConverter. Como resultado, EF Core convierte los valores de cadena de la columna en miembros correspondientes del tipo de enumeración de .NET. Sin embargo, si el valor de cadena no coincidía con el miembro de enumeración, la propiedad se establecía en el valor predeterminado de la enumeración.

Comportamiento nuevo

EF Core 6.0 ahora produce un InvalidOperationException con el mensaje "Cannot convert string value '{value}' from the database to any value in the mapped '{enumType}' enum".

Por qué

La conversión al valor predeterminado puede provocar daños en la base de datos si la entidad se guarda posteriormente en la base de datos.

Mitigaciones

Lo ideal es asegurarse de que la columna de la base de datos solo contenga valores válidos. Como alternativa, implemente ValueConverter con el comportamiento anterior.

DbFunctionBuilder.HasTranslation ahora proporciona los argumentos de función como IReadOnlyList en lugar de IReadOnlyCollection

Incidencia de seguimiento n.º 23565

Comportamiento anterior

Al configurar la traducción para una función definida por el usuario mediante el método HasTranslation, los argumentos de la función se proporcionaban como IReadOnlyCollection<SqlExpression>.

Comportamiento nuevo

En EF Core 6.0, los argumentos ahora se proporcionan como IReadOnlyList<SqlExpression>.

Por qué

IReadOnlyList permite usar indexadores, por lo que ahora es más fácil acceder a los argumentos.

Mitigaciones

Ninguno. IReadOnlyList implementa la interfaz IReadOnlyCollection, por lo que la transición debería ser sencilla.

La asignación de tabla predeterminada no se quita cuando la entidad se asigna a una función con valores de tabla

Incidencia de seguimiento n.º 23408

Comportamiento anterior

Cuando se asignaba una entidad a una función con valores de tabla, se quitaba su asignación predeterminada a una tabla.

Comportamiento nuevo

En EF Core 6.0, la entidad se sigue asignando a una tabla mediante la asignación predeterminada, incluso si también se asigna a una función con valores de tabla.

Por qué

Las funciones con valores de tabla que devuelven entidades a menudo se usan como asistente o para encapsular una operación que devuelve una colección de entidades, en lugar de usarse como un reemplazo estricto de toda la tabla. Este cambio pretende ajustarse más a la intención probable del usuario.

Mitigaciones

La asignación a una tabla se puede deshabilitar explícitamente en la configuración del modelo:

modelBuilder.Entity<MyEntity>().ToTable((string)null);

dotnet-ef tiene como destino .NET 6

Seguimiento de la incidencia #27787

Comportamiento anterior

El comando dotnet-ef ha tenido como destino .NET Core 3.1 por un tiempo. Esto le permitió usar la versión más reciente de la herramienta sin instalar versiones más recientes del entorno de ejecución .NET.

Comportamiento nuevo

En EF Core 6.0.6, la herramienta dotnet-ef tiene como destino .NET 6. Todavía puede usar la herramienta en proyectos destinados a versiones anteriores de .NET y .NET Core, pero deberá instalar el entorno de ejecución de .NET 6 para ejecutar la herramienta.

Por qué

El SDK de .NET 6.0.200 actualizó el comportamiento de dotnet tool install en osx-arm64 para crear una corrección de compatibilidad de osx-x64 para herramientas destinadas a .NET Core 3.1. Para mantener una experiencia predeterminada de trabajo para dotnet-ef, tuvimos que actualizarlo para tener .NET 6 como destino.

Mitigaciones

Para ejecutar dotnet-ef sin instalar el entorno de ejecución de .NET 6, puede instalar una versión anterior de la herramienta:

dotnet tool install dotnet-ef --version 3.1.*

Es posible que sea necesario actualizar las implementaciones IModelCacheKeyFactory para controlar el almacenamiento en caché en tiempo de diseño

Incidencia de seguimiento n.º 25154

Comportamiento anterior

IModelCacheKeyFactory no tenía ninguna opción para almacenar en caché el modelo en tiempo de diseño independientemente del modelo en tiempo de ejecución.

Comportamiento nuevo

IModelCacheKeyFactory tiene una nueva sobrecarga que permite que el modelo en tiempo de diseño se almacene en caché por separado del modelo en tiempo de ejecución. No implementar este método puede dar lugar a una excepción similar a la siguiente:

System.InvalidOperationException: "La configuración solicitada no se almacena en el modelo optimizado para lectura, use "DbContext.GetService<IDesignTimeModel>(). Model'.'

Por qué

La implementación de modelos compilados requería la separación del tiempo de diseño (que se usa al compilar el modelo) y el tiempo de ejecución (que se usa al ejecutar consultas, etc.). Si el código en tiempo de ejecución necesita acceso a la información en tiempo de diseño, el modelo en tiempo de diseño debe almacenarse en caché.

Mitigaciones

Implemente la nueva sobrecarga. Por ejemplo:

public object Create(DbContext context, bool designTime)
    => context is DynamicContext dynamicContext
        ? (context.GetType(), dynamicContext.UseIntProperty, designTime)
        : (object)context.GetType();

La navegación ”{navigation}” se omitió de ”Include” en la consulta, ya que la corrección la rellenará automáticamente. Si posteriormente se especifican más navegaciones en "Include", se omitirán. No está permitido retroceder en el árbol de inclusión.

Incidencia de seguimiento n.º 4315

Comportamiento anterior

El evento CoreEventId.NavigationBaseIncludeIgnored se registró como una advertencia de forma predeterminada.

Comportamiento nuevo

El evento CoreEventId.NavigationBaseIncludeIgnored se registró como un error de forma predeterminada y hace que se produzca una excepción.

Por qué

No se permiten estos patrones de consulta, por lo que EF Core ahora inicia excepciones para indicar que las consultas deben actualizarse.

Mitigaciones

El comportamiento anterior se puede restaurar configurando el evento como una advertencia. Por ejemplo:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Warn(CoreEventId.NavigationBaseIncludeIgnored));