Cambios importantes en EF Core 7.0 (EF7)
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 6 a EF Core 7. 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 7.0 está orientado a .NET 6. Esto significa que las aplicaciones existentes orientadas a .NET 6 pueden seguir haciéndolo. Las aplicaciones orientadas a versiones anteriores de .NET, .NET Core y .NET Framework tendrán que orientarse a .NET 6 o .NET 7 para utilizar EF Core 7.0.
Resumen
Cambios de impacto alto
Encrypt
por defecto true
para conexiones SQL Server
Problema de seguimiento: SqlClient #1210
Importante
Se trata de un cambio de ruptura grave en el paquete Microsoft.Data.SqlClient. No hay nada que se pueda hacer en EF Core para revertir o mitigar este cambio. Dirija sus comentarios a Microsoft.Data.SqlClient GitHub Repo o póngase en contacto con un Microsoft Support ProfessionalVisual Studio Professional para preguntas adicionales o ayuda.
Comportamiento anterior
Cadenas de conexión SqlClient utilizan Encrypt=False
por defecto. Esto permite conexiones en máquinas de desarrollo donde el servidor local no tiene un certificado válido.
Comportamiento nuevo
Cadenas de conexión SqlClient utilizan Encrypt=True
por defecto. Esto significa que:
- El servidor debe estar configurado con un certificado válido
- El cliente debe confiar en este certificado
Si no se cumplen estas condiciones, se producirá un SqlException
. Por ejemplo:
Se estableció correctamente una conexión con el servidor, pero luego se produjo un error durante el proceso de inicio de sesión. (proveedor: Proveedor SSL, error: 0 - La cadena de certificados fue emitida por una autoridad que no es de confianza).
Por qué
Este cambio se realizó para garantizar que, por defecto, o la conexión es segura o la aplicación fallará al conectarse.
Mitigaciones
Hay tres formas de proceder:
- Instalar un certificado válido en el servidor. Tenga en cuenta que se trata de un proceso complejo que requiere obtener un certificado y asegurarse de que está firmado por una autoridad en la que confía el cliente.
- Si el servidor tiene un certificado, pero no es de confianza para el cliente, entonces
TrustServerCertificate=True
para permitir eludir el mecanismo de confianza normal. - Agregar explícitamente
Encrypt=False
a la cadena de conexión.
Advertencia
Las opciones 2 y 3 dejan al servidor en un estado potencialmente inseguro.
Algunos avisos vuelven a lanzar excepciones por defecto
Incidencia de seguimiento nº 29069
Comportamiento anterior
En EF Core 6.0, un error en el proveedor de SQL Server hacía que algunas advertencias configuradas para lanzar excepciones de forma predeterminada se registraran pero no lanzaran excepciones. Estas advertencias son:
EventId | Descripción |
---|---|
RelationalEventId.AmbientTransactionWarning | Una aplicación puede haber esperado que se utilizara una transacción ambiental cuando en realidad fue ignorada. |
RelationalEventId.IndexPropertiesBothMappedAndNotMappedToTable | Un índice especifica propiedades, algunas de las cuales están asignadas y otras no, a una columna de una tabla. |
RelationalEventId.IndexPropertiesMappedToNonOverlappingTables | Un índice especifica propiedades que se asignan a columnas de tablas que no están superpuestas. |
RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables | Una clave externa especifica propiedades que no se asignan a las tablas relacionadas. |
Comportamiento nuevo
A partir de EF Core 7.0, estas advertencias vuelven a provocar, por defecto, el lanzamiento de una excepción.
Por qué
Se trata de problemas que muy probablemente indican un error en el código de la aplicación que debería corregirse.
Mitigaciones
Corrija el problema subyacente que motiva la advertencia.
Alternativamente, se puede cambiar el nivel de advertencia para que solo se registre o se suprima por completoy. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Ignore(RelationalEventId.AmbientTransactionWarning));
Las tablas de SQL Server con desencadenadores o determinadas columnas computadas requieren ahora una configuración especial de EF Core
Incidencia de seguimiento nº 27372
Comportamiento anterior
Las versiones anteriores del proveedor de SQL Server guardaban los cambios mediante una técnica menos eficaz que siempre funcionaba.
Comportamiento nuevo
Por defecto, EF Core ahora guarda los cambios a través de una técnica significativamente más eficiente; desafortunadamente, esta técnica no es compatible con SQL Server si la tabla de destino tiene triggers de base de datos, o ciertos tipos de columnas computadas. Consulte la documentación de SQL Server para obtener más detalles.
Por qué
Las mejoras de rendimiento ligadas al nuevo método son lo suficientemente significativas como para que sea importante ofrecerlas a los usuarios por defecto. Al mismo tiempo, estimamos que el uso de disparadores de bases de datos o de las columnas calculadas afectadas en las aplicaciones EF Core es lo suficientemente bajo como para que las consecuencias negativas de los cambios de rotura se vean compensadas por la ganancia de rendimiento.
Mitigaciones
A partir de EF Core 8.0, el uso o no de la cláusula "OUTPUT" se puede configurar explícitamente. Por ejemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.ToTable(tb => tb.UseSqlOutputClause(false));
}
En EF7 o posterior, si la tabla de destino tiene un desencadenador, puede informar a EF Core de conocerlo y EF revertirá a la técnica anterior, menos eficaz. Esto puede hacerse configurando el tipo de entidad correspondiente del siguiente modo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.ToTable(tb => tb.HasTrigger("SomeTrigger"));
}
Tenga en cuenta que esto no hace que EF Core cree o administre el desencadenador de ninguna manera: actualmente solo informa a EF Core de que hay desencadenadores presentes en la tabla. Como resultado, se puede usar cualquier nombre de desencadenador. Especificar un desencadenador se puede usar para revertir el comportamiento anterior incluso si realmente no hay un desencadenador en la tabla.
Si la mayoría o todas sus tablas tienen desencadenadores, puede optar por no utilizar la técnica más reciente y eficaz para todas las tablas de su modelo utilizando la siguiente convención de creación de modelos:
public class BlankTriggerAddingConvention : IModelFinalizingConvention
{
public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
if (table != null
&& entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(table.Value) == null)
&& (entityType.BaseType == null
|| entityType.GetMappingStrategy() != RelationalAnnotationNames.TphMappingStrategy))
{
entityType.Builder.HasTrigger(table.Value.Name + "_Trigger");
}
foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
{
if (entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(fragment.StoreObject) == null))
{
entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger");
}
}
}
}
}
Utilice la convención en su DbContext
anulando ConfigureConventions
:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Add(_ => new BlankTriggerAddingConvention());
}
De este modo, se accede a todas las tablas del modelo HasTrigger
, en lugar de tener que hacerlo manualmente para cada una de ellas.
Las tablas SQLite con desencadenadores AFTER y las tablas virtuales requieren ahora una configuración especial de EF Core
Incidencia de seguimiento nº 29916
Comportamiento anterior
Las versiones anteriores del proveedor de SQLite guardaban los cambios mediante una técnica menos eficiente que siempre funcionaba.
Comportamiento nuevo
Por defecto, EF Core ahora guarda los cambios a través de una técnica más eficiente, utilizando la cláusula RETURNING. Desafortunadamente, esta técnica no es compatible con SQLite si la tabla de destino tiene triggers AFTER de base de datos, es virtual o si se están utilizando versiones antiguas de SQLite. Consulte la documentación de SQLite para más detalles.
Por qué
Las simplificaciones y mejoras de rendimiento ligadas al nuevo método son lo suficientemente significativas como para que sea importante ponerlas a disposición de los usuarios por defecto. Al mismo tiempo, estimamos que el uso de desencadenadores de bases de datos y tablas virtuales en aplicaciones EF Core es lo suficientemente bajo como para que las consecuencias negativas de los cambios de rotura se vean compensadas por la ganancia de rendimiento.
Mitigaciones
En EF Core 8.0, el método UseSqlReturningClause
se ha introducido para volver explícitamente a la versión anterior y menos eficaz de SQL. Por ejemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.ToTable(tb => tb.UseSqlReturningClause(false));
}
Si todavía usa EF Core 7.0, es posible revertir al mecanismo anterior para toda la aplicación insertando el código siguiente en la configuración del contexto:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlite(...)
.ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();
Cambios de impacto medio
Los dependientes huérfanos de relaciones opcionales no se eliminan automáticamente
Incidencia de seguimiento nº 27217
Comportamiento anterior
Una relación es opcional si su clave externa es anulable. Establecer la clave externa como nula permite que la entidad dependiente exista sin ninguna entidad principal relacionada. Las relaciones opcionales pueden ser configuradas para usar eliminación en cascada, aunque esto no es lo predeterminado.
Un dependiente opcional puede separarse de su principal estableciendo su clave externa en null, o borrando la navegación hacia o desde él. En EF Core 6.0, esto provocaba la eliminación del dependiente cuando la relación estaba configurada para la eliminación en cascada.
Comportamiento nuevo
A partir de EF Core 7.0, el dependiente ya no se elimina. Tenga en cuenta que si se elimina el principal, el dependiente también se eliminará, ya que la relación está configurada para la eliminación en cascada.
Por qué
El dependiente puede existir sin ninguna relación con un principal, por lo que la ruptura de la relación no debería provocar la eliminación de la entidad.
Mitigaciones
El dependiente puede borrarse explícitamente:
context.Remove(blog);
O SaveChanges
puede ser anulado o interceptado para eliminar dependientes sin referencia principal. Por ejemplo:
context.SavingChanges += (c, _) =>
{
foreach (var entry in ((DbContext)c!).ChangeTracker
.Entries<Blog>()
.Where(e => e.State == EntityState.Modified))
{
if (entry.Reference(e => e.Author).CurrentValue == null)
{
entry.State = EntityState.Deleted;
}
}
};
Se configura la eliminación en cascada entre tablas cuando se utiliza el mapeo TPT con SQL Server
Incidencia de seguimiento nº 28532
Comportamiento anterior
Cuando se asigna una jerarquía de herencia utilizando la estrategia TPT, la tabla base debe contener una fila para cada entidad guardada, independientemente del tipo real de dicha entidad. La eliminación de la fila de la tabla base debería eliminar las filas de todas las demás tablas. EF Core configura una eliminación en cascada para esto.
En EF Core 6.0, un error en el proveedor de bases de datos SQL Server impedía la creación de estas eliminaciones en cascada.
Comportamiento nuevo
A partir de EF Core 7.0, las eliminaciones en cascada se crean ahora para SQL Server como siempre se han hecho para otras bases de datos.
Por qué
La eliminación en cascada desde la tabla base a las subtablas en TPT permite eliminar una entidad borrando su fila en la tabla base.
Mitigaciones
En la mayoría de los casos, este cambio no debería causar ningún problema. Sin embargo, SQL Server es muy restrictivo cuando hay múltiples comportamientos en cascada configurados entre tablas. Esto significa que si existe una relación en cascada entre tablas en la asignación TPT, SQL Server puede generar el siguiente error:
Microsoft.Data.SqlClient.SqlException: La sentencia DELETE entró en conflicto con la restricción REFERENCE "FK_Blogs_People_OwnerId". El conflicto ha aparecido en la base de datos "Scratch", tabla "dbo.Blogs", columna "OwnerId". Se terminó la instrucción.
Por ejemplo, este modelo crea un ciclo de relaciones en cascada:
[Table("FeaturedPosts")]
public class FeaturedPost : Post
{
public int ReferencePostId { get; set; }
public Post ReferencePost { get; set; } = null!;
}
[Table("Posts")]
public class Post
{
public int Id { get; set; }
public string? Title { get; set; }
public string? Content { get; set; }
}
Uno de ellos tendrá que ser configurado para no utilizar eliminaciones en cascada en el servidor. Por ejemplo, para cambiar la relación explícita:
modelBuilder
.Entity<FeaturedPost>()
.HasOne(e => e.ReferencePost)
.WithMany()
.OnDelete(DeleteBehavior.ClientCascade);
O para cambiar la relación implícita creada para la asignación TPT:
modelBuilder
.Entity<FeaturedPost>()
.HasOne<Post>()
.WithOne()
.HasForeignKey<FeaturedPost>(e => e.Id)
.OnDelete(DeleteBehavior.ClientCascade);
Mayor probabilidad de errores de ocupado/bloqueado en SQLite cuando no se utiliza el registro de escritura anticipada
Comportamiento anterior
Las versiones anteriores del proveedor SQLite guardaron los cambios a través de una técnica menos eficiente que podía reintentar automáticamente cuando la tabla estaba bloqueada/ocupada y el registro de escritura anticipada (WAL) no estaba habilitado.
Comportamiento nuevo
Por defecto, EF Core ahora guarda los cambios a través de una técnica más eficiente, utilizando la cláusula RETURNING. Desafortunadamente, esta técnica no puede reintentar automáticamente cuando está ocupado/bloqueado. En una aplicación multiproceso (como una aplicación web) que no utiliza el registro de escritura anticipada, es común encontrar estos errores.
Por qué
Las simplificaciones y mejoras de rendimiento ligadas al nuevo método son lo suficientemente significativas como para que sea importante ponerlas a disposición de los usuarios por defecto. Las bases de datos creadas por EF Core también habilitan el registro de escritura anticipada de forma predeterminada. El equipo de SQLite también recomienda habilitar el registro de escritura anticipada de forma predeterminada.
Mitigaciones
Si es posible, debe habilitar el registro de escritura anticipada en su base de datos. Si su base de datos fue creada por EF, este ya debería ser el caso. De lo contrario, puede habilitar el registro de escritura anticipada ejecutando el siguiente comando.
PRAGMA journal_mode = 'wal';
Si, por algún motivo, no puede habilitar el registro de escritura anticipada, es posible volver al mecanismo anterior para toda la aplicación insertando el siguiente código en su configuración de contexto:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlite(...)
.ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();
Cambios de impacto bajo
Puede ser necesario configurar las propiedades de las claves con un comparador de valores del proveedor
Incidencia de seguimiento nº 27738
Comportamiento anterior
En EF Core 6.0, los valores clave tomados directamente de las propiedades de los tipos de entidad se utilizaban para comparar los valores clave al guardar los cambios. Esto haría uso de cualquier comparador de valores personalizado configurado en estas propiedades.
Comportamiento nuevo
A partir de EF Core 7.0, se utilizan los valores de la base de datos para estas comparaciones. Esto "simplemente funciona" en la inmensa mayoría de los casos. Sin embargo, si las propiedades estaban utilizando un comparador personalizado, y ese comparador no se puede aplicar a los valores de la base de datos, entonces puede ser necesario un "comparador de valores del proveedor", como se muestra a continuación.
Por qué
Varias divisiones de entidades y tablas pueden dar lugar a múltiples propiedades asignadas a la misma columna de la base de datos, y viceversa. Esto requiere que los valores se comparen después de la conversión al valor que se utilizará en la base de datos.
Mitigaciones
Configurar un comparador de valores de proveedores. Por ejemplo, considere el caso en el que un objeto de valor se está utilizando como clave, y el comparador para esa clave utiliza comparaciones de cadenas insensibles a mayúsculas/minúsculas:
var blogKeyComparer = new ValueComparer<BlogKey>(
(l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
v => v.Id.ToUpper().GetHashCode(),
v => v);
var blogKeyConverter = new ValueConverter<BlogKey, string>(
v => v.Id,
v => new BlogKey(v));
modelBuilder.Entity<Blog>()
.Property(e => e.Id).HasConversion(
blogKeyConverter, blogKeyComparer);
Los valores de la base de datos (cadenas) no pueden utilizar directamente el comparador definido para los tipos BlogKey
. Por lo tanto, debe configurarse un comparador de proveedores para comparaciones de cadenas sin distinción entre mayúsculas y minúsculas:
var caseInsensitiveComparer = new ValueComparer<string>(
(l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
v => v.ToUpper().GetHashCode(),
v => v);
var blogKeyComparer = new ValueComparer<BlogKey>(
(l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
v => v.Id.ToUpper().GetHashCode(),
v => v);
var blogKeyConverter = new ValueConverter<BlogKey, string>(
v => v.Id,
v => new BlogKey(v));
modelBuilder.Entity<Blog>()
.Property(e => e.Id).HasConversion(
blogKeyConverter, blogKeyComparer, caseInsensitiveComparer);
Ahora las restricciones de verificación y otras facetas de la tabla están configuradas en la tabla
Incidencia de seguimiento nº 28205
Comportamiento anterior
En EF Core 6.0, HasCheckConstraint
, HasComment
, y IsMemoryOptimized
se llamaban directamente en el creador de tipos de entidad. Por ejemplo:
modelBuilder
.Entity<Blog>()
.HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023");
modelBuilder
.Entity<Blog>()
.HasComment("It's my table, and I'll delete it if I want to.");
modelBuilder
.Entity<Blog>()
.IsMemoryOptimized();
Comportamiento nuevo
A partir de EF Core 7.0, estos métodos se invocan en el generador de tablas:
modelBuilder
.Entity<Blog>()
.ToTable(b => b.HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023"));
modelBuilder
.Entity<Blog>()
.ToTable(b => b.HasComment("It's my table, and I'll delete it if I want to."));
modelBuilder
.Entity<Blog>()
.ToTable(b => b.IsMemoryOptimized());
Los métodos existentes se han marcado como Obsolete
. Actualmente tienen el mismo comportamiento que los nuevos métodos, pero se eliminarán en una futura versión.
Por qué
Estas facetas solo se aplican a las tablas. No se aplicarán a ninguna vista, función o procedimiento almacenado asignados.
Mitigaciones
Utilice los métodos de creación de tablas, como se muestra arriba.
Las navegaciones desde entidades nuevas a entidades eliminadas no están arregladas
Incidencia de seguimiento nº 28249
Comportamiento anterior
En EF Core 6.0, cuando se rastrea una nueva entidad, ya sea a partir de una consulta de rastreo o adjuntándola al DbContext
, las navegaciones hacia y desde entidades relacionadas en el Deleted
estado se reparan.
Comportamiento nuevo
A partir de EF Core 7.0, las navegaciones hacia y desde las entidades Deleted
no están arregladas.
Por qué
Una vez que una entidad se marca como Deleted
rara vez tiene sentido asociarla con entidades no eliminadas.
Mitigaciones
Consulte o adjunte entidades antes de marcarlas como Deleted
, o establezca manualmente las propiedades de navegación hacia y desde la entidad eliminada.
Usar FromSqlRaw
y métodos relacionados del proveedor equivocado lanza use-the-correct-method
Incidencia de seguimiento nº 26502
Comportamiento anterior
En EF Core 6.0, el uso del método de extensión Azure Cosmos DB FromSqlRaw cuando se utiliza un proveedor relacional, o el método de extensión relacional FromSqlRaw cuando se utiliza el proveedor Azure Cosmos DB podría fallar silenciosamente. Del mismo modo, el uso de métodos relacionales en el proveedor en memoria es una operación sin operación silenciosa.
Comportamiento nuevo
A partir de EF Core 7.0, el uso de un método de extensión diseñado para un proveedor en otro proveedor producirá una excepción.
Por qué
Debe utilizarse el método de extensión correcto para que funcione correctamente en todas las situaciones.
Mitigaciones
Utilice el método de extensión correcto para el proveedor utilizado. Si se hace referencia a varios proveedores, llame al método de extensión como método estático. Por ejemplo:
var result = CosmosQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();
O:
var result = RelationalQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();
Scaffolded OnConfiguring
ya no llama IsConfigured
Incidencia de seguimiento nº 4274
Comportamiento anterior
En EF Core 6.0, el tipo DbContext
creado a partir de una base de datos existente contenía una llamada a IsConfigured
. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
optionsBuilder.UseNpgsql("MySecretConnectionString");
}
}
Comportamiento nuevo
A partir de EF Core 7.0, ya no se incluye la llamada a IsConfigured
.
Por qué
Hay escenarios muy limitados donde el proveedor de base de datos se configura dentro de su DbContext en algunos casos, pero solo si el contexto no está configurado ya. En cambio, si se deja OnConfiguring
aquí es más probable que se deje en el código una cadena de conexión que contenga información confidencial, a pesar de la advertencia en tiempo de compilación. Por lo tanto, la seguridad adicional y el código más limpio de la eliminación de este se consideró que valía la pena, sobre todo teniendo en cuenta que el --no-onconfiguring
(.NET CLI) o -NoOnConfiguring
(Visual Studio Package Manager Console) bandera se puede utilizar para evitar el andamiaje del método OnConfiguring
, y que existen plantillas personalizables para agregar de nuevo IsConfigured
si es realmente necesario.
Mitigaciones
O bien:
- Utilice el argumento
--no-onconfiguring
(.NET CLI) o-NoOnConfiguring
(Visual Studio Package Manager Console) al crear un andamiaje a partir de una base de datos existente. - Personalice las plantillas T4 para volver a agregar la llamada a
IsConfigured
.