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
* 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:
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(); });
Asegúrese de que el dependiente contiene al menos una propiedad necesaria.
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.
Azure Cosmos DB: Los tipos de entidad relacionados se detectan como en propiedad
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 |
Cascade | CASCADE |
ClientCascade | |
SetNull | SET NULL |
ClientSetNull |
Los cambios de scaffolding son los siguientes.
ON DELETE | OnDelete() |
---|---|
NO ACTION | ClientSetNull |
RESTRICT | |
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:
- Asegurarse de que las consultas de seguimiento y sin seguimiento tienen un comportamiento coherente.
- 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:
- 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
Include
Foo.Bar
si la instanciaBar
no debe devolverse de la base de datos e incluirse en los resultados. - 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.
NavigationBaseIncludeIgnored
ahora es un error de forma predeterminada
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));