Cambios importantes en EF Core 9 (EF9)
En esta página se documentan los cambios de comportamiento de la API y que pueden interrumpir la actualización de aplicaciones existentes de EF Core 8 a EF Core 9. Asegúrese de revisar los cambios importantes anteriores si va a actualizar desde una versión anterior de EF Core:
Marco de destino
EF Core 9 se ha orientado a .NET 8. Esto significa que las aplicaciones existentes que estén orientadas a .NET 8 pueden seguir estándolo. Las aplicaciones orientadas a versiones anteriores de .NET, .NET Core y .NET Framework tendrán que orientarse a .NET 8 o .NET 9 para utilizar EF Core 9.
Resumen
Nota:
Si usa Azure Cosmos DB, consulte la sección independiente sobre cambios importantes en Azure Cosmos DB.
Cambios de impacto alto
Se produce una excepción al aplicar migraciones si hay cambios pendientes en el modelo.
Problema de seguimiento n.º 33732
Comportamiento anterior
Si el modelo tiene cambios pendientes en comparación con la última migración, no se aplican con el resto de las migraciones cuando se llama a Migrate
.
Comportamiento nuevo
A partir de EF Core 9.0, si el modelo tiene cambios pendientes en comparación con la última migración, se produce una excepción cuando se llama a dotnet ef database update
, Migrate
o MigrateAsync
:
El modelo del contexto "DbContext" tiene cambios pendientes. Agregue una nueva migración antes de actualizar la base de datos. Esta excepción se puede suprimir o registrar pasando el identificador de evento "RelationalEventId.PendingModelChangesWarning" al método "ConfigureWarnings" en "DbContext.OnConfiguring" o "AddDbContext".
Por qué
Olvidar agregar una nueva migración después de realizar cambios en el modelo es un error común que puede ser difícil de diagnosticar en algunos casos. La nueva excepción garantiza que el modelo de la aplicación coincida con la base de datos después de aplicar las migraciones.
Mitigaciones
Hay varias situaciones comunes en las que se puede producir esta excepción:
- No hay migraciones en absoluto. Esto es común cuando la base de datos se actualiza a través de otros medios.
- Mitigación: si no tiene previsto usar migraciones para administrar el esquema de la base de datos, quite la llamada
Migrate
oMigrateAsync
; de lo contrario, agregue una migración.
- Mitigación: si no tiene previsto usar migraciones para administrar el esquema de la base de datos, quite la llamada
- Hay al menos una migración, pero falta la instantánea del modelo. Esto es habitual para las migraciones creadas manualmente.
- Mitigación: agregue una nueva migración mediante las herramientas de EF, lo que actualizará la instantánea del modelo.
- El desarrollador no modificó el modelo, pero está integrado de forma no determinista, lo que hace que EF lo detecte como modificado. Esto es habitual cuando
new DateTime()
,DateTime.Now
,DateTime.UtcNow
oGuid.NewGuid()
se usan en objetos proporcionados paraHasData()
.- mitigación: agregue una nueva migración, examine su contenido para localizar la causa y reemplace los datos dinámicos por un valor estático codificado de forma estática en el modelo. La migración deberá recrearse después de que se corrija el modelo. Si se deben usar datos dinámicos para la propagación, considere la posibilidad de usar el nuevo patrón de propagación en lugar de
HasData()
.
- mitigación: agregue una nueva migración, examine su contenido para localizar la causa y reemplace los datos dinámicos por un valor estático codificado de forma estática en el modelo. La migración deberá recrearse después de que se corrija el modelo. Si se deben usar datos dinámicos para la propagación, considere la posibilidad de usar el nuevo patrón de propagación en lugar de
- La última migración se creó para un proveedor diferente al usado para aplicar las migraciones.
- Mitigación: se trata de un escenario no admitido. La advertencia se puede suprimir mediante el fragmento de código siguiente, pero es probable que este escenario deje de funcionar en una versión futura de EF Core. La solución recomendada es para generar un conjunto independiente de migraciones para cada proveedor.
- Las migraciones se generan o seleccionan dinámicamente al reemplazar algunos servicios de EF.
Mitigación: la advertencia es un falso positivo en este caso y debe omitirse:
options.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning))
Si el escenario no se encuentra en ninguno de los casos anteriores y cada vez que agrega una nueva migración se crea la misma migración o una migración vacía y la excepción todavía se produce, cree un pequeño proyecto de reproducción y compártalo con el equipo de EF en un nuevo problema.
Se produce una excepción al aplicar migraciones en una transacción explícita
Problema de Seguimiento #17578
Comportamiento anterior
Para aplicar migraciones resistentemente, se usó el siguiente patrón:
await dbContext.Database.CreateExecutionStrategy().ExecuteAsync(async () =>
{
await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
await dbContext.Database.MigrateAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
});
Comportamiento nuevo
A partir de EF Core 9.0, las llamadas Migrate
y MigrateAsync
iniciarán una transacción y ejecutarán los comandos mediante un ExecutionStrategy
y, si la aplicación usa el patrón anterior, se produce una excepción:
Se generó un error relacionado con la advertencia "Microsoft.EntityFrameworkCore.Migrations.MigrationsUserTransactionWarning": se inició una transacción antes de aplicar migraciones. Esto evita que se adquiera un bloqueo de base de datos y, por tanto, la base de datos no se protegerá de aplicaciones de migración simultáneas. EF ya administra las transacciones y la estrategia de ejecución según sea necesario. Elimine la transacción externa. Esta excepción se puede suprimir o registrar pasando el identificador de evento "RelationalEventId.MigrationsUserTransactionWarning" al método "ConfigureWarnings" en "DbContext.OnConfiguring" o "AddDbContext".
Por qué
El uso de una transacción explícita impide que se adquiera un bloqueo de base de datos y, por tanto, la base de datos no se protegerá de aplicaciones de migración simultáneas, también limita EF sobre cómo puede administrar las transacciones internamente.
Mitigaciones
Si hay solo una llamada a la base de datos dentro de la transacción, elimine la transacción externa y ExecutionStrategy
:
await dbContext.Database.MigrateAsync(cancellationToken);
De lo contrario, si el escenario requiere una transacción explícita y tiene otro mecanismo implementado para evitar la aplicación de migración simultánea, omita la advertencia:
options.ConfigureWarnings(w => w.Ignore(RelationalEventId.MigrationsUserTransactionWarning))
Cambios de impacto bajo
EF.Functions.Unhex()
ahora devuelve byte[]?
Incidencia de seguimiento n.º 33864
Comportamiento anterior
La función EF.Functions.Unhex()
antes anotada para devolver byte[]
.
Comportamiento nuevo
A partir de EF Core 9.0, Unhex() ahora se anota para devolver byte[]?
.
Por qué
Unhex()
se traduce a la función SQLite unhex
, que devuelve NULL para entradas no válidas. Como resultado, Unhex()
devuelve null
para esos casos, en violación de la anotación.
Mitigaciones
Si está seguro de que el contenido de texto pasado Unhex()
representa una cadena hexadecimal válida, puede simplemente añadir el operador null-forgiving (permite valores null) como afirmación de que la invocación nunca devolverá un valor null:
var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync();
De lo contrario, agregue comprobaciones en tiempo de ejecución para null en el valor devuelto de Unhex().
Arity de los argumentos de nulabilidad de SqlFunctionExpression validados
Incidencia de seguimiento n.º 33852
Comportamiento anterior
Anteriormente era posible crear un SqlFunctionExpression
con un número diferente de argumentos y argumentos de propagación de nulabilidad.
Comportamiento nuevo
A partir de EF Core 9.0, EF ahora inicia si el número de argumentos y argumentos de propagación de nulabilidad no coinciden.
Por qué
No tener un número coincidente de argumentos y argumentos de propagación de nulabilidad puede provocar un comportamiento inesperado.
Mitigaciones
Asegúrese de que tiene el argumentsPropagateNullability
mismo número de elementos que arguments
. Cuando se duda se usa false
para el argumento de nulabilidad.
el método ToString()
devuelve ahora una cadena vacía para las instancias null
Incidencia de seguimiento n.º 33941
Comportamiento anterior
Anteriormente EF devolvía resultados inconsistentes para el método ToString()
cuando el valor del argumento era null
. Por ejemplo, ToString()
en la propiedad bool?
con valor devuelto null
null
, pero para expresiones no propiedad bool?
cuyo valor era null
devolvía True
. El comportamiento también era inconsistente para otros tipos de datos, por ejemplo, ToString()
en el valor enum null
devolvía cadena vacía.
Comportamiento nuevo
A partir de EF Core 9.0, el método ToString()
devuelve ahora sistemáticamente una cadena vacía en todos los casos en que el valor del argumento es null
.
Por qué
El comportamiento anterior era incoherente en diferentes tipos de datos y situaciones, además de no coincidir con el comportamiento de C#.
Mitigaciones
Para volver al comportamiento anterior, reescriba la consulta en consecuencia:
var newBehavior = context.Entity.Select(x => x.NullableBool.ToString());
var oldBehavior = context.Entity.Select(x => x.NullableBool == null ? null : x.NullableBool.ToString());
Las dependencias del marco compartido se actualizaron a 9.0.x
Comportamiento anterior
Las aplicaciones que utilizan el SDK Microsoft.NET.Sdk.Web
y están orientadas a net8.0 resolverían paquetes como System.Text.Json
, Microsoft.Extensions.Caching.Memory
, Microsoft.Extensions.Configuration.Abstractions
, Microsoft.Extensions.Logging
y Microsoft.Extensions.DependencyModel
desde el marco compartido, por lo que estos ensamblados no se implementarían normalmente con la aplicación.
Comportamiento nuevo
Aunque EF Core 9.0 sigue siendo compatible con net8.0, ahora hace referencia a las versiones 9.0.x de System.Text.Json
, Microsoft.Extensions.Caching.Memory
, Microsoft.Extensions.Configuration.Abstractions
, Microsoft.Extensions.Logging
y Microsoft.Extensions.DependencyModel
. Las aplicaciones orientadas a net8.0 no podrán aprovechar el marco compartido para evitar la implantación de estos ensamblados.
Por qué
Las versiones de dependencia coincidentes contienen las últimas correcciones de seguridad y su uso simplifica el modelo de servicio para EF Core.
Mitigaciones
Cambie su aplicación a la versión de destino net9.0 para obtener el comportamiento anterior.
Cambios de última hora en Azure Cosmos DB
Se ha realizado un gran esfuerzo para mejorar el proveedor Azure Cosmos DB en la versión 9.0. Los cambios incluyen una serie de cambios importantes de alto impacto. Si está actualizando una aplicación existente, lea atentamente lo siguiente.
Cambios de impacto alto
La propiedad discriminator ahora se llama $type
en lugar de Discriminator
Problema de seguimiento n.º 34269
Comportamiento anterior
EF añade automáticamente una propiedad discriminadora a los documentos JSON para identificar el tipo de entidad que representa el documento. En versiones anteriores de EF, esta propiedad JSON solía llamarse Discriminator
por defecto.
Comportamiento nuevo
A partir de EF Core 9.0, la propiedad discriminator se llama ahora $type
por defecto. Si tiene documentos existentes en Azure Cosmos DB de versiones anteriores de EF, estos utilizan la antigua nomenclatura Discriminator
y, después de actualizar a EF 9.0, las consultas contra esos documentos fallarán.
Por qué
Una práctica emergente de JSON usa una propiedad $type
en situaciones en las que es necesario identificar el tipo de documento. Por ejemplo, System.Text.Json de .NET también soporta polimorfismo, usando $type
como discriminador por defecto el nombre de la propiedad (docs). Para alinearse con el resto del ecosistema y facilitar la interoperabilidad con herramientas externas, se ha cambiado el valor por defecto.
Mitigaciones
La mitigación más fácil es simplemente configurar el nombre de la propiedad discriminador para que sea Discriminator
, igual que antes:
modelBuilder.Entity<Session>().HasDiscriminator<string>("Discriminator");
Hacer esto para todos sus tipos de entidad de nivel superior hará que EF se comporte igual que antes.
En este punto, si lo desea, también puede actualizar todos sus documentos para utilizar la nueva nomenclatura $type
.
La propiedad id
ahora contiene solo la propiedad clave de EF por defecto
Problema de seguimiento n.º 34179
Comportamiento anterior
Anteriormente, EF insertaba el valor discriminante de su tipo de entidad en la propiedad id
del documento. Por ejemplo, si guardaba un tipo de entidad Blog
con una propiedad Id
que contenía 8, la propiedad id
JSON contendría Blog|8
.
Comportamiento nuevo
A partir de EF Core 9.0, la propiedad JSON id
ya no contiene el valor discriminador, y solo contiene el valor de su propiedad clave. En el ejemplo anterior, la propiedad JSON id
sería simplemente 8
. Si tiene documentos existentes en Azure Cosmos DB de versiones anteriores de EF, estos tienen el valor discriminador en la propiedad JSON id
, y después de actualizar a EF 9.0, las consultas sobre esos documentos fallarán.
Por qué
Dado que la propiedad JSON id
debe ser única, el discriminador se añadió anteriormente para permitir la existencia de diferentes entidades con el mismo valor clave. Por ejemplo, esto permitía tener tanto a Blog
como a Post
con una propiedad Id
que contuviera el valor 8 dentro del mismo contenedor y partición. Esto se ajustaba mejor a los patrones de modelado de datos de las bases de datos relacionales, donde cada tipo de entidad se asigna a su propia tabla y, por lo tanto, tiene su propio espacio de claves.
En general, EF 9.0 cambió la asignación para que se ajustara más a las prácticas y expectativas habituales de Azure Cosmos DB NoSQL, en lugar de corresponderse con las expectativas de los usuarios procedentes de bases de datos relacionales. Además, tener el valor discriminador en la propiedad id
dificultaba la interacción de herramientas y sistemas externos con los documentos JSON generados por EF. Estos sistemas externos no suelen conocer los valores discriminadores de EF, que derivan por defecto de tipos .NET.
Mitigaciones
La solución más sencilla es simplemente configurar EF para que incluya el discriminador en la propiedad JSON id
, como antes. Para ello se ha introducido una nueva opción de configuración:
modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();
Hacer esto para todos sus tipos de entidad de nivel superior hará que EF se comporte igual que antes.
En este punto, si lo desea, también puede actualizar todos sus documentos para reescribir su propiedad JSON id
. Tenga en cuenta que esto solo es posible si las entidades de diferentes tipos no comparten el mismo valor de identificador dentro del mismo contenedor.
La propiedad JSON id
se asigna a la clave.
Problema de seguimiento n.º 34179
Comportamiento anterior
Anteriormente, EF creó una propiedad sombra mapeada a la propiedad JSON id
, a menos que una de las propiedades se mapease a id
explícitamente.
Comportamiento nuevo
A partir de EF Core 9, la propiedad de clave se asignará a la propiedad JSON id
por convención, si es posible. Esto significa que la propiedad clave ya no se conservará en el documento con un nombre diferente y con el mismo valor, por lo que el código que no sea EF que consuma los documentos y dependa de la presencia de esta propiedad ya no funcionará correctamente.
Por qué
En general, EF 9.0 cambió la asignación para alinearse más con las prácticas y expectativas comunes de NoSQL de Azure Cosmos DB. Y no es habitual almacenar el valor de clave dos veces en el documento.
Mitigaciones
Si desea conservar el comportamiento de EF Core 8, la mitigación más sencilla es usar una nueva opción de configuración que se ha introducido para este propósito:
modelBuilder.Entity<Session>().HasShadowId();
Hacer esto para todos sus tipos de entidad de nivel superior hará que EF se comporte igual que antes. O bien, puede aplicarlo a todos los tipos de entidad del modelo con una llamada:
modelBuilder.HasShadowIds();
Cambios de impacto medio
Ya no se admite la sincronización de E/S a través del proveedor de Azure Cosmos DB
Incidencia de seguimiento n.º 32563
Comportamiento anterior
Anteriormente, llamar a métodos sincrónicos como ToList
o SaveChanges
haría que EF Core se bloqueara sincrónicamente mediante .GetAwaiter().GetResult()
al ejecutar llamadas asincrónicas en el SDK de Azure Cosmos DB. Esto puede provocar un interbloqueo.
Comportamiento nuevo
A partir de EF Core 9.0, EF ahora lanza una excepción de forma predeterminada al intentar usar la E/S sincrónica. El mensaje de excepción es "Azure Cosmos DB no admite E/S sincrónica. Asegúrese de usar y esperar correctamente solo métodos asincrónicos al usar Entity Framework Core para acceder a Azure Cosmos DB. Para más información, vea https://aka.ms/ef-cosmos-nosync".
Por qué
El bloqueo sincrónico en métodos asincrónicos puede dar lugar a interbloqueo y el SDK de Azure Cosmos DB solo admite métodos asincrónicos.
Mitigaciones
En EF Core 9.0, el error se puede suprimir con:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ConfigureWarnings(w => w.Ignore(CosmosEventId.SyncNotSupported));
}
Dicho esto, las aplicaciones deberían dejar de usar las API de sincronización con Azure Cosmos DB, ya que no es compatible con el SDK de Azure Cosmos DB. La capacidad de suprimir la excepción se quitará en una versión futura de EF Core, después de la cual la única opción será usar API asincrónicas.
Las consultas SQL ahora deben proyectar valores JSON directamente
Problema de seguimiento n.º 25527
Comportamiento anterior
Anteriormente, EF generaba consultas como la siguiente:
SELECT c["City"] FROM root c
Estas consultas hacen que Azure Cosmos DB encapsule cada resultado en un objeto JSON, como se muestra a continuación:
[
{
"City": "Berlin"
},
{
"City": "México D.F."
}
]
Comportamiento nuevo
A partir de EF Core 9.0, EF añade ahora el modificador VALUE
a las consultas de la siguiente forma:
SELECT VALUE c["City"] FROM root c
Estas consultas hacen que Azure Cosmos DB devuelva los valores directamente, sin encapsularlos:
[
"Berlin",
"México D.F."
]
Si su aplicación hace uso de consultas SQL, es probable que dichas consultas no funcionen tras la actualización a EF 9.0, ya que no incluyen el modificador VALUE
.
Por qué
Encapsular cada resultado en un objeto JSON adicional puede causar una degradación del rendimiento en algunos escenarios, aumenta la carga útil del resultado JSON y no es la forma natural de trabajar con Azure Cosmos DB.
Mitigaciones
Para evitarlo, basta con añadir el modificador VALUE
a las proyecciones de las consultas SQL, como se muestra más arriba.
Los resultados no definidos ahora se filtran automáticamente de los resultados de las consultas
Problema de seguimiento n.º 25527
Comportamiento anterior
Anteriormente, EF generaba consultas como la siguiente:
SELECT c["City"] FROM root c
Estas consultas hacen que Azure Cosmos DB encapsule cada resultado en un objeto JSON, como se muestra a continuación:
[
{
"City": "Berlin"
},
{
"City": "México D.F."
}
]
Si alguno de los resultados era indefinido (por ejemplo, la propiedad City
no estaba presente en el documento), se devolvía un documento vacío y EF regresaba null
para ese resultado.
Comportamiento nuevo
A partir de EF Core 9.0, EF añade ahora el modificador VALUE
a las consultas de la siguiente forma:
SELECT VALUE c["City"] FROM root c
Estas consultas hacen que Azure Cosmos DB devuelva los valores directamente, sin encapsularlos:
[
"Berlin",
"México D.F."
]
El comportamiento de Azure Cosmos DB consiste en filtrar automáticamente los valores undefined
de los resultados; esto significa que si una de las propiedades City
no aparece en el documento, la consulta devolverá un único resultado, en lugar de dos resultados, uno de los cuales será null
.
Por qué
Encapsular cada resultado en un objeto JSON adicional puede causar una degradación del rendimiento en algunos escenarios, aumenta la carga útil del resultado JSON y no es la forma natural de trabajar con Azure Cosmos DB.
Mitigaciones
Si obtener valores null
para resultados no definidos es importante para su aplicación, agrupe los valores undefined
en null
con el nuevo operador EF.Functions.Coalesce
:
var users = await context.Customer
.Select(c => EF.Functions.CoalesceUndefined(c.City, null))
.ToListAsync();
Las consultas traducidas incorrectamente ya no se traducen
Problema de seguimiento n.º 34123
Comportamiento anterior
Anteriormente, EF traducía consultas como la siguiente:
var sessions = await context.Sessions
.Take(5)
.Where(s => s.Name.StartsWith("f"))
.ToListAsync();
Sin embargo, la traducción SQL de esta consulta era incorrecta:
SELECT c
FROM root c
WHERE ((c["Discriminator"] = "Session") AND STARTSWITH(c["Name"], "f"))
OFFSET 0 LIMIT @__p_0
En SQL, la cláusula WHERE
se evalúa antes que las cláusulas OFFSET
y LIMIT
, pero en la consulta LINQ anterior, el operador Take
aparece antes que el operador Where
. Como resultado, estas consultas podrían devolver resultados incorrectos.
Comportamiento nuevo
A partir de EF Core 9.0, estas consultas ya no se traducen y se lanza una excepción.
Por qué
Las traducciones incorrectas pueden causar corrupción silenciosa de datos, lo que puede introducir errores difíciles de detectar en su aplicación. EF siempre prefiere fracasar y responder rápido a los errores lanzándose por adelantado antes que causar posiblemente corrupción de datos.
Mitigaciones
Si estuviera satisfecho con el comportamiento anterior y quiere ejecutar el mismo SQL, simplemente cambie el orden de los operadores LINQ:
var sessions = await context.Sessions
.Where(s => s.Name.StartsWith("f"))
.Take(5)
.ToListAsync();
Lamentablemente, Azure Cosmos DB no soporta actualmente las cláusulas OFFSET
y LIMIT
en las subconsultas SQL, que es lo que requiere la traducción correcta de la consulta LINQ original.
Cambios de impacto bajo
HasIndex
ahora se lanza en lugar de ser ignorado
Problema de seguimiento n.º 34023
Comportamiento anterior
Anteriormente, las llamadas a HasIndex eran ignoradas por el proveedor de EF Cosmos DB.
Comportamiento nuevo
Ahora el proveedor lanza si HasIndex se especifica.
Por qué
En Azure Cosmos DB, todas las propiedades están indexadas por defecto y no es necesario especificar ninguna indexación. Aunque es posible definir una política de indexación personalizada, esto no está soportado actualmente por EF, y se puede hacer a través del Azure Portal sin soporte de EF. Dado que las llamadas HasIndex no eran útiles, ya no están permitidas.
Mitigaciones
Elimine cualquier llamada a HasIndex.
IncludeRootDiscriminatorInJsonId
fue renombrado a HasRootDiscriminatorInJsonId
después de 9.0.0-rc.2
Problema de seguimiento n.º 34717
Comportamiento anterior
La API IncludeRootDiscriminatorInJsonId
se introdujo en 9.0.0 rc.1.
Comportamiento nuevo
Para la versión final de EF Core 9.0, se cambió el nombre de la API a HasRootDiscriminatorInJsonId
Por qué
Se renombró otra API relacionada para que empezara por Has
en lugar de Include
, por lo que esta también fue renombrada por coherencia.
Mitigaciones
Si su código usa la API IncludeRootDiscriminatorInJsonId
, simplemente cámbielo para que referencie a HasRootDiscriminatorInJsonId
en su lugar.