Cambios importantes incluidos en EF Core 3.x

Es posible que los siguientes cambios de API y comportamiento interrumpan las aplicaciones existentes cuando se actualicen a las versiones 3.x. Los cambios que esperamos que solo afecten a proveedores de base de datos se documentan en Cambios para proveedores.

Resumen

Cambio importante Impacto
Las consultas LINQ ya no se evalúan en el cliente Alto
La herramienta de línea de comandos de EF Core, dotnet ef, ya no forma parte del SDK de .NET Core Alto
DetectChanges respeta los valores de clave generados por el almacén Alto
FromSql, ExecuteSql y ExecuteSqlAsync han cambiado de nombre Alto
Los tipos de consulta se consolidan con tipos de entidad Alto
Entity Framework Core ya no forma parte del marco compartido ASP.NET Core Media
Las eliminaciones en cascada ahora se realizan inmediatamente de forma predeterminada Media
La carga diligente de entidades relacionadas ahora se realiza en una sola consulta Media
DeleteBehavior.Restrict tiene una semántica más limpia Media
La API de configuración para las relaciones de tipo de propiedad ha cambiado Media
Cada propiedad usa la generación de claves enteras en memoria independiente Media
Las consultas sin seguimiento ya no realizan la resolución de la identidad Media
Cambios en la API de metadatos Media
Cambios en la API de metadatos específicos del proveedor Media
Se ha quitado el elemento UseRowNumberForPaging Media
Cuando el método FromSql se usa con un procedimiento almacenado no se puede redactar Media
Solo se pueden especificar métodos de FromSql en raíces de consulta Bajo
Los valores de clave temporal ya no se establecen en instancias de entidad Bajo
Las entidades dependientes que comparten la tabla con la entidad de seguridad son ahora opcionales Bajo
Todas las entidades que compartan una tabla con una columna de token de simultaneidad tienen que asignarla a una propiedad Bajo
Las entidades en propiedad no se pueden consultar sin el propietario mediante una consulta de seguimiento Bajo
Las propiedades heredadas de tipos sin asignar se asignan ahora a una única columna para todos los tipos derivados Bajo
La convención de propiedad de clave externa ya no coincide con el mismo nombre que la propiedad de entidad de seguridad Bajo
La conexión de base de datos ahora se cierra si ya no se usa antes de que se complete TransactionScope Bajo
Los campos de respaldo se usan de forma predeterminada Bajo
Inicio de excepciones si se encuentran varios campos de respaldo compatibles Bajo
Los nombres de propiedades de solo campo deben coincidir con el nombre del campo Bajo
AddDbContext/AddDbContextPool ya no llaman a AddLogging ni a AddMemoryCache Bajo
AddEntityFramework* agrega IMemoryCache con un límite de tamaño Bajo
DbContext.Entry realiza ahora una operación DetectChanges local Bajo
El cliente no genera las claves de matriz de cadena y byte de forma predeterminada Bajo
ILoggerFactory es ahora un servicio con ámbito Bajo
En los proxies de carga diferida ya no se supone que las propiedades de navegación están totalmente cargadas Bajo
La creación excesiva de proveedores de servicios internos ahora es un error de forma predeterminada Bajo
Comportamiento nuevo de HasOne/HasMany llamado con una sola cadena Bajo
El tipo de valor devuelto para varios métodos asincrónicos se ha cambiado de Task a ValueTask Bajo
La anotación Relational:TypeMapping ahora es simplemente TypeMapping Bajo
ToTable en un tipo derivado produce una excepción Bajo
EF Core ya no envía pragma para el cumplimiento de SQLite FK Bajo
Microsoft.EntityFrameworkCore.Sqlite ahora depende de SQLitePCLRaw.bundle_e_sqlite3 Bajo
Los valores GUID se almacenan ahora como TEXT en SQLite Bajo
Los valores char se almacenan ahora como TEXT en SQLite Bajo
Los id. de migración ahora se generan usando el calendario de la referencia cultural invariable Bajo
La información o los metadatos de la extensión se han quitado de IDbContextOptionsExtension Bajo
LogQueryPossibleExceptionWithAggregateOperator ha cambiado de nombre Bajo
Clarificación de la API para nombres de restricciones de claves externas Bajo
IRelationalDatabaseCreator.HasTables/HasTablesAsync se han hecho públicos Bajo
Microsoft.EntityFrameworkCore.Design es ahora un paquete DevelopmentDependency Bajo
SQLitePCL.raw se ha actualizado a la versión 2.0.0 Bajo
NetTopologySuite se actualizó a la versión 2.0.0 Bajo
Se usa Microsoft.Data.SqlClient en lugar de System.Data.SqlClient Bajo
Se deben configurar varias relaciones de referencia automática ambiguas Bajo
DbFunction.Schema es NULL o la cadena vacía lo configura para estar en el esquema predeterminado del modelo Bajo
EF Core 3.0 tiene como destino .NET Standard 2.1, y no .NET Standard 2.0 Revertido
La ejecución de consultas se registra en el nivel de depuración Revertido

Cambios de impacto alto

Las consultas LINQ ya no se evalúan en el cliente

Problema de seguimiento n.° 14935Consulte también el problema n.° 12795

Comportamiento anterior

Antes de 3.0, cuando en EF Core no se podía convertir una expresión que formaba parte de una consulta SQL o un parámetro, la expresión se evaluaba de forma automática en el cliente. De forma predeterminada, la evaluación de cliente de expresiones potencialmente costosas solo desencadenaba una advertencia.

Comportamiento nuevo

A partir de 3.0, en EF Core solo se permite que se evalúen en el cliente las expresiones en la proyección de nivel superior (la última llamada a Select() de la consulta). Cuando las expresiones de otra parte de la consulta no se pueden convertir en SQL o un parámetro, se inicia una excepción.

Por qué

La evaluación de cliente automática de las consultas permite que se ejecuten muchas consultas incluso si no se pueden convertir elementos importantes de ellas. Esto puede provocar un comportamiento inesperado y potencialmente dañino que es posible que solo sea evidente en entornos de producción. Por ejemplo, una condición en una llamada a Where() que no se puede convertir puede provocar que todas las filas de la tabla se transfieran desde el servidor de base de datos y que el filtro se aplique en el cliente. Esta situación puede pasar desapercibida fácilmente si la tabla solo contiene algunas filas en la fase de desarrollo, pero ser más grave cuando la aplicación pase a producción, donde la tabla puede contener millones de filas. Las advertencias de evaluación de cliente también se suelen pasar por alto durante el desarrollo.

Además de esto, la evaluación de cliente automática puede causar problemas en los que la mejora de la traducción de consultas para expresiones específicas provocaba cambios importantes no deseados entre versiones.

Mitigaciones

Si una consulta no se puede traducir totalmente, vuelva a escribirla en un formato que se pueda traducir, o bien use AsEnumerable(), ToList() o una función similar para devolver los datos al cliente de forma explícita, donde después se puedan seguir procesando mediante LINQ to Objects.

Cambios de impacto medio

Entity Framework Core ya no forma parte del marco compartido ASP.NET Core

Anuncios del problema de seguimiento n.º 325

Comportamiento anterior

Antes de ASP.NET Core 3.0, cuando se agregaba una referencia de paquete a Microsoft.AspNetCore.App o Microsoft.AspNetCore.All, se incluía EF Core y algunos de los proveedores de datos de EF Core, como el de SQL Server.

Comportamiento nuevo

A partir de la versión 3.0, el marco compartido ASP.NET Core no incluye EF Core ni ningún proveedor de datos de EF Core.

Por qué

Antes de este cambio, para obtener EF Core se necesitaban varios pasos en función de si la aplicación se destinaba a ASP.NET Core y SQL Server o no. Además, la actualización de ASP.NET Core forzaba la de EF Core y el proveedor de SQL Server, lo que no siempre es deseable.

Con este cambio, la experiencia de obtención de EF Core es la misma en todos los proveedores, implementaciones admitidas de .NET y tipos de aplicación. Ahora los desarrolladores también pueden controlar exactamente cuándo se actualizan EF Core y los proveedores de datos de EF Core.

Mitigaciones

Para usar EF Core en una aplicación ASP.NET Core 3.0 o cualquier otra aplicación compatible, debe agregar de forma explícita una referencia de paquete al proveedor de base de datos de EF Core que se va a usar en la aplicación.

La herramienta de línea de comandos de EF Core, dotnet ef, ya no forma parte del SDK de .NET Core

Problema de seguimiento n.º 14016

Comportamiento anterior

Antes de 3.0, la herramienta dotnet ef se incluía en el SDK de .NET Core y estaba disponible para usarse desde la línea de comandos de cualquier proyecto sin necesidad de realizar pasos adicionales.

Comportamiento nuevo

A partir de la versión 3.0, el SDK de .NET no incluye la herramienta dotnet ef, por lo que antes de poder usarla tendrá que instalarla de forma explícita como una herramienta local o global.

Por qué

Este cambio nos permite distribuir y actualizar dotnet ef como una herramienta convencional de la CLI de .NET en NuGet, coherente con el hecho de que la versión 3.0 de EF Core también se distribuye siempre como un paquete NuGet.

Mitigaciones

Para poder administrar las migraciones o aplicar scaffolding a DbContext, instale dotnet-ef como herramienta global:

dotnet tool install --global dotnet-ef

También se puede obtener una herramienta local cuando se restauran las dependencias de un proyecto que la declara como una dependencia de herramientas mediante un archivo de manifiesto de herramientas.

Cambios de impacto bajo

FromSql, ExecuteSql y ExecuteSqlAsync han cambiado de nombre

Problema de seguimiento n.º 10996

Importante

ExecuteSqlCommand y ExecuteSqlCommandAsync están en desuso. En su lugar, use estos métodos.

Comportamiento anterior

Antes de EF Core 3.0, estos nombres de métodos se sobrecargaban para funcionar tanto con una cadena normal como con una cadena que se debería interpolar en SQL y parámetros.

Comportamiento nuevo

A partir de la versión EF Core 3.0, use FromSqlRaw, ExecuteSqlRaw y ExecuteSqlRawAsync para crear una consulta con parámetros donde los parámetros se pasan por separado de la cadena de consulta. Por ejemplo:

context.Products.FromSqlRaw(
    "SELECT * FROM Products WHERE Name = {0}",
    product.Name);

Use FromSqlInterpolated, ExecuteSqlInterpolated y ExecuteSqlInterpolatedAsync para crear una consulta con parámetros donde los parámetros se pasan como parte de una cadena de consulta interpolada. Por ejemplo:

context.Products.FromSqlInterpolated(
    $"SELECT * FROM Products WHERE Name = {product.Name}");

Tenga en cuenta que las dos consultas anteriores producirán el mismo código SQL parametrizado con los mismos parámetros SQL.

Por qué

Las sobrecargas del método como esta facilitan las llamadas accidentales al método de cadena sin procesar cuando la intención era llamar al método de cadena interpolada y viceversa. Esto podría resultar en consultas que no se parametrizan cuando deberían.

Mitigaciones

Haga el cambio para usar los nuevos nombres de métodos.

Cuando el método FromSql se usa con un procedimiento almacenado no se puede redactar

Problema de seguimiento n.° 15392

Comportamiento anterior

Antes de EF Core 3.0, el método FromSql intentaba detectar si se podía redactar en el código SQL pasado. Cuando el código SQL no se podía redactar, como un procedimiento almacenado, realizaba la evaluación de cliente. La consulta siguiente funcionaba al ejecutar el procedimiento almacenado en el servidor y aplicar FirstOrDefault en el lado cliente.

context.Products.FromSqlRaw("[dbo].[Ten Most Expensive Products]").FirstOrDefault();

Comportamiento nuevo

A partir de EF Core 3.0, EF Core no intentará analizar el código SQL. Por tanto, si va a redactar después de FromSqlRaw/FromSqlInterpolated, EF Core redactará el código SQL generando una subconsulta. Por tanto, si usa un procedimiento almacenado con la redacción, obtendrá una excepción de sintaxis de SQL no válida.

Por qué

EF Core 3.0 no admite la evaluación automática de cliente, ya que era propenso a errores, como se explica aquí.

Mitigaciones

Si usa un procedimiento almacenado en FromSqlRaw/FromSqlInterpolated, sabe que no se puede redactar, por lo que puede agregar AsEnumerable/AsAsyncEnumerable justo después de la llamada al método FromSql para evitar cualquier redacción en el lado servidor.

context.Products.FromSqlRaw("[dbo].[Ten Most Expensive Products]").AsEnumerable().FirstOrDefault();

Solo se pueden especificar métodos de FromSql en raíces de consulta

Problema de seguimiento n.° 15704

Comportamiento anterior

Antes de EF Core 3.0, el método FromSql podía especificarse en cualquier lugar en la consulta.

Comportamiento nuevo

A partir de EF Core 3.0, los nuevos métodos FromSqlRaw y FromSqlInterpolated (que reemplazan a FromSql) solo pueden especificarse en las raíces de la consulta, es decir, directamente en DbSet<>. Si intenta especificarlos en cualquier otro lugar se producirá un error de compilación.

Por qué

La especificación de FromSql en cualquier otro lugar diferente de DbSet no tenía un significado o valor agregado, y podría causar ambigüedad en ciertos escenarios.

Mitigaciones

Las invocaciones de FromSql se deben mover para que estén directamente en el DbSet al que se aplican.

Las consultas sin seguimiento ya no realizan la resolución de la identidad

Problema de seguimiento n.º 13518

Comportamiento anterior

Antes de EF Core 3.0, se usaba la misma instancia de la entidad para cada aparición de una entidad con un tipo e identificador determinados. Este comportamiento coincide con el de las consultas de seguimiento. Fijémonos en esta consulta:

var results = context.Products.Include(e => e.Category).AsNoTracking().ToList();

Esta consulta devolverá la misma instancia de Category para cada elemento Product asociado con la categoría determinada.

Comportamiento nuevo

A partir de EF Core 3.0, se crean distintas instancias de la entidad si se encuentra una entidad con un tipo e identificador determinados en varias ubicaciones del gráfico devuelto. Por ejemplo, la consulta anterior ahora devolverá una nueva instancia de Category para cada elemento Product cuando haya dos productos asociados a la misma categoría.

Por qué

La resolución de las identidades (es decir, el hecho de determinar que una entidad tiene los mismos tipo e identificador que la entidad encontrada) agrega más rendimiento y sobrecarga de memoria. Este enfoque suele ser contrario a por qué las consultas sin seguimiento se usan en primer lugar. Además, aunque la resolución de las identidades a veces puede resultar útil, no es necesaria si las entidades se van a serializar y enviar a un cliente, algo habitual para las consultas sin seguimiento.

Mitigaciones

Si se requiere la resolución de identidad, use una consulta de seguimiento.

Los valores de clave temporal ya no se establecen en instancias de entidad

Problema de seguimiento n.º 12378

Comportamiento anterior

Antes de EF Core 3.0, los valores temporales se asignaban a todas las propiedades de clave para las que posteriormente la base de datos generaba un valor real. Normalmente, estos valores temporales eran números negativos grandes.

Comportamiento nuevo

A partir de la versión 3.0, en EF Core se almacena el valor de clave temporal como parte de la información de seguimiento de la entidad y la propiedad clave en sí no se modifica.

Por qué

Este cambio se ha realizado para evitar que los valores de clave temporales se conviertan erróneamente en permanentes cuando una entidad de la que previamente una instancia de DbContext ha realizado el seguimiento se mueve a otra instancia de DbContext.

Mitigaciones

Las aplicaciones que asignan valores de clave principal a claves externas para crear asociaciones entre entidades pueden depender del comportamiento anterior si las claves principales son generadas por el almacén y pertenecen a entidades en el estado Added. Esto se puede evitar con las siguientes situaciones:

  • No se usan claves generadas por el almacén.
  • Se establecen propiedades de navegación para crear relaciones en lugar de establecer valores de clave externa.
  • Se obtienen los valores de clave temporal reales de la información de seguimiento de la entidad. Por ejemplo, context.Entry(blog).Property(e => e.Id).CurrentValue devolverá el valor temporal aunque no se haya establecido blog.Id.

DetectChanges respeta los valores de clave generados por el almacén

Problema de seguimiento n.º 14616

Comportamiento anterior

Antes de EF Core 3.0, se realizaba el seguimiento en el estado Added de las entidades sin seguimiento detectadas por DetectChanges y se insertaban como una fila nueva cuando se llamaba a SaveChanges.

Comportamiento nuevo

A partir de EF Core 3.0, si una entidad usa valores de clave generados y se establece un valor de clave, se realizará el seguimiento de la entidad en el estado Modified. Esto significa que se supone que existe una fila para la entidad y que se actualizará cuando se llame a SaveChanges. Si no se establece el valor de clave, o bien si el tipo de entidad no usa claves generadas, se seguirá realizando el seguimiento de la entidad nueva como Added al igual que en las versiones anteriores.

Por qué

Este cambio se ha realizado para que sea más sencillo y coherente trabajar con gráficos de entidades desconectadas mientras se usan claves generadas por el almacén.

Mitigaciones

Este cambio puede interrumpir una aplicación si se configura un tipo de entidad para usar claves generadas, pero se establecen de forma explícita valores de clave para las instancias nuevas. La solución consiste en configurar de forma explícita las propiedades de clave para que no usen valores generados. Por ejemplo, con la API fluida:

modelBuilder
    .Entity<Blog>()
    .Property(e => e.Id)
    .ValueGeneratedNever();

O bien con anotaciones de datos:

[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }

Las eliminaciones en cascada ahora se realizan inmediatamente de forma predeterminada

Problema de seguimiento n.º 10114

Comportamiento anterior

Antes de la versión 3.0, en EF Core no se aplicaban acciones en cascada (eliminación de entidades dependientes cuando se eliminaba una entidad de seguridad obligatoria o cuando se rompía la relación con una entidad de seguridad obligatoria) hasta que se llamaba a SaveChanges.

Comportamiento nuevo

A partir de 3.0, en EF Core las acciones en cascada se aplican en cuanto se detecta la condición desencadenadora. Por ejemplo, como resultado de la llamada a context.Remove() para eliminar una entidad de seguridad, todos los dependientes obligatorios relacionados de los que se realiza el seguimiento también se establecen en Deleted inmediatamente.

Por qué

Este cambio se ha realizado para mejorar la experiencia en escenarios de auditoría y enlace de datos donde es importante comprender qué entidades se van a eliminar antes de llamar a SaveChanges.

Mitigaciones

El comportamiento anterior se puede restaurar mediante opciones de context.ChangeTracker. Por ejemplo:

context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;
context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;

Problema de seguimiento n.º 18022

Comportamiento anterior

Antes de la versión 3.0, la carga diligente de navegaciones de colección a través de operadores Include provocaba la generación de varias consultas en la base de datos relacional, una para cada tipo de entidad relacionada.

Comportamiento nuevo

A partir de la versión 3.0, EF Core genera una sola consulta con operadores JOIN en las bases de datos relacionales.

Por qué

La emisión de varias consultas para implementar una única consulta LINQ provocaba numerosos problemas, incluido el rendimiento negativo, ya que se necesitaban varios recorridos de ida y vuelta a la base de datos, y problemas de coherencia de datos, ya que cada consulta podía observar un estado distinto de la base de datos.

Mitigaciones

Aunque técnicamente esto no es un cambio importante, podría tener un efecto considerable en el rendimiento de la aplicación cuando una sola consulta contiene un gran número de operadores Include en las navegaciones de la colección. Vea este comentario para obtener más información y para volver a escribir las consultas de una manera más eficaz.

**

DeleteBehavior.Restrict tiene una semántica más limpia

Problema de seguimiento n.º 12661

Comportamiento anterior

Antes de la versión 3.0, DeleteBehavior.Restrict creaba claves externas en la base de datos con la semántica Restrict, pero también realizaba una corrección interna de manera no evidente.

Comportamiento nuevo

A partir de la versión 3.0, DeleteBehavior.Restrict garantiza que las claves externas se crean con la semántica Restrict, es decir, sin cascadas y realizando una infracción de restricción, sin llevar a cabo correcciones internas de EF.

Por qué

Este cambio se realizó para mejorar la experiencia de uso de DeleteBehavior de manera intuitiva sin efectos secundarios inesperados.

Mitigaciones

El comportamiento anterior se puede restaurar con DeleteBehavior.ClientNoAction.

Los tipos de consulta se consolidan con tipos de entidad

Problema de seguimiento n.º 14194

Comportamiento anterior

Antes de EF Core 3.0, los tipos de consulta eran un medio para consultar los datos que no definen una clave principal de una manera estructurada. Es decir, un tipo de consulta se usaba para asignar tipos de entidad sin claves (más probablemente desde una vista, pero posiblemente desde una tabla), mientras que un tipo de entidad estándar se usaba cuando había una clave disponible (más probablemente desde una tabla, pero posiblemente desde una vista).

Comportamiento nuevo

Ahora un tipo de consulta se convierte en un tipo de entidad sin una clave principal. Los tipos de entidad sin clave tienen la misma funcionalidad que los tipos de consulta de las versiones anteriores.

Por qué

Este cambio se ha realizado para reducir la confusión en torno a la finalidad de los tipos de consulta. En concreto, son tipos de entidad sin clave y, por ello, intrínsecamente son de solo lectura, pero no se deben usar solo porque un tipo de entidad tenga que ser de solo lectura. Del mismo modo, se suelen asignar a vistas, pero solo porque las vistas no suelen definir claves.

Mitigaciones

Los elementos siguientes de la API ahora están obsoletos:

  • ModelBuilder.Query<>(): en su lugar es necesario llamar a ModelBuilder.Entity<>().HasNoKey() para marcar un tipo de entidad como sin claves. Esto todavía no se configurará por convención para evitar una configuración incorrecta cuando se espera una clave principal, pero no coincide con la convención.
  • DbQuery<>: en su lugar se debe usar DbSet<>.
  • DbContext.Query<>(): en su lugar se debe usar DbContext.Set<>().
  • IQueryTypeConfiguration<TQuery>: en su lugar se debe usar IEntityTypeConfiguration<TEntity>.

Nota:

Debido a un problema en la versión 3.x, al consultar entidades sin clave que tienen todas las propiedades establecidas en null se devolverá un null en lugar de una entidad, si este problema es aplicable a su escenario, agregue también lógica para administrar null en los resultados.

La API de configuración para las relaciones de tipo de propiedad ha cambiado

Problema de seguimiento n.º 12444Problema de seguimiento n.º 9148Problema de seguimiento n.º 14153

Comportamiento anterior

Antes de EF Core 3.0, la configuración de la relación de propiedad se realizaba directamente después de la llamada a OwnsOne o OwnsMany.

Comportamiento nuevo

A partir de EF Core 3.0, ahora hay una API fluida para configurar una propiedad de navegación para el propietario mediante WithOwner(). Por ejemplo:

modelBuilder.Entity<Order>.OwnsOne(e => e.Details).WithOwner(e => e.Order);

La configuración relacionada con la relación entre el propietario y lo que se posee ahora se debe encadenar después de WithOwner(), de forma similar a cómo se configuran otras relaciones. Pero la configuración del propio tipo de propiedad se seguirá encadenando después de OwnsOne()/OwnsMany(). Por ejemplo:

modelBuilder.Entity<Order>.OwnsOne(e => e.Details, eb =>
    {
        eb.WithOwner()
            .HasForeignKey(e => e.AlternateId)
            .HasConstraintName("FK_OrderDetails");

        eb.ToTable("OrderDetails");
        eb.HasKey(e => e.AlternateId);
        eb.HasIndex(e => e.Id);

        eb.HasOne(e => e.Customer).WithOne();

        eb.HasData(
            new OrderDetails
            {
                AlternateId = 1,
                Id = -1
            });
    });

Además, la llamada a Entity(), HasOne() o Set() con un tipo de propiedad de destino ahora iniciará una excepción.

Por qué

Este cambio se ha realizado para crear una separación más clara entre la configuración del propio tipo de propiedad y la relación con el tipo de propiedad. A su vez, esto elimina la ambigüedad y la confusión de métodos como HasForeignKey.

Mitigaciones

Cambie la configuración de las relaciones de tipo de propiedad para usar la nueva superficie de API, como se muestra en el ejemplo anterior.

Ahora, las entidades dependientes que comparten la tabla con la entidad de seguridad son opcionales

Problema de seguimiento n.º 9005

Comportamiento anterior

Considere el modelo siguiente:

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public OrderDetails Details { get; set; }
}

public class OrderDetails
{
    public int Id { get; set; }
    public string ShippingAddress { get; set; }
}

Antes de EF Core 3.0, si OrderDetails era propiedad de Order o estaba asignado explícitamente a la misma tabla, siempre era necesaria una instancia de OrderDetails al agregar un elemento Order nuevo.

Comportamiento nuevo

A partir de la versión 3.0, EF Core permite agregar Order sin OrderDetails y asigna todas las propiedades OrderDetails a excepción de la clave principal a columnas que aceptan valores NULL. Al realizar consultas, EF Core establece OrderDetails en null si ninguna de las propiedades necesarias tiene un valor o si no tiene propiedades necesarias más allá de la clave principal y todas las propiedades son null.

Mitigaciones

Si el modelo tiene una tabla que comparte dependencias con todas las columnas opcionales, pero la navegación que apunta a ella no se espera que sea null, la aplicación debería modificarse para controlar los casos en los que la navegación sea null. Si esto no es posible, debería agregarse una propiedad necesaria al tipo de entidad o, al menos, una entidad debería tener un valor distinto a null asignado.

Todas las entidades que compartan una tabla con una columna de token de simultaneidad tienen que asignarla a una propiedad

Problema de seguimiento n.º 14154

Comportamiento anterior

Considere el modelo siguiente:

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public byte[] Version { get; set; }
    public OrderDetails Details { get; set; }
}

public class OrderDetails
{
    public int Id { get; set; }
    public string ShippingAddress { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Order>()
        .Property(o => o.Version).IsRowVersion().HasColumnName("Version");
}

Antes de EF Core 3.0, si OrderDetails era propiedad de Order o estaba asignado explícitamente a la misma tabla, si solo se actualizaba OrderDetails, no se actualizaba el valor Version en el cliente y se producía un error en la próxima actualización.

Comportamiento nuevo

A partir de la versión 3.0, EF Core propaga el nuevo valor Version en Order si posee OrderDetails. En caso contrario, se produce una excepción durante la validación del modelo.

Por qué

Este cambio se realizó para evitar un valor de token de simultaneidad obsoleto cuando solo se actualiza una de las entidades asignadas a la misma tabla.

Mitigaciones

Todas las entidades que comparten la tabla deben incluir una propiedad que se asigna a la columna del token de simultaneidad. Es posible crear una en estado reemplazado:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<OrderDetails>()
        .Property<byte[]>("Version").IsRowVersion().HasColumnName("Version");
}

Las entidades en propiedad no se pueden consultar sin el propietario mediante una consulta de seguimiento

Problema de seguimiento n.º 18876

Comportamiento anterior

Antes de EF Core 3.0, las entidades en propiedad se podían consultar como cualquier otra navegación.

context.People.Select(p => p.Address);

Comportamiento nuevo

A partir de la versión 3.0, EF Core iniciará una excepción si una consulta de seguimiento proyecta una entidad en propiedad sin el propietario.

Por qué

Las entidades en propiedad no se pueden manipular sin el propietario, por lo que en la mayoría de los casos es un error consultarlas de esta manera.

Mitigaciones

Si se debe realizar el seguimiento de la entidad en propiedad para modificarla de cualquier manera posterior, el propietario se debe incluir en la consulta.

De lo contrario, agregue una llamada a AsNoTracking():

context.People.Select(p => p.Address).AsNoTracking();

Las propiedades heredadas de tipos sin asignar se asignan ahora a una única columna para todos los tipos derivados

Problema de seguimiento n.º 13998

Comportamiento anterior

Considere el modelo siguiente:

public abstract class EntityBase
{
    public int Id { get; set; }
}

public abstract class OrderBase : EntityBase
{
    public int ShippingAddress { get; set; }
}

public class BulkOrder : OrderBase
{
}

public class Order : OrderBase
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Ignore<OrderBase>();
    modelBuilder.Entity<EntityBase>();
    modelBuilder.Entity<BulkOrder>();
    modelBuilder.Entity<Order>();
}

Antes de EF Core 3.0, la propiedad ShippingAddress se asignaba a columnas distintas para BulkOrder y Order de forma predeterminada.

Comportamiento nuevo

A partir de la versión3.0, EF Core solo crea una columna para ShippingAddress.

Por qué

El comportamiento anterior no era el esperado.

Mitigaciones

Todavía se puede asignar explícitamente la propiedad a columnas separadas en los tipos derivados:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Ignore<OrderBase>();
    modelBuilder.Entity<EntityBase>();
    modelBuilder.Entity<BulkOrder>()
        .Property(o => o.ShippingAddress).HasColumnName("BulkShippingAddress");
    modelBuilder.Entity<Order>()
        .Property(o => o.ShippingAddress).HasColumnName("ShippingAddress");
}

La convención de propiedad de clave externa ya no coincide con el mismo nombre que la propiedad de entidad de seguridad

Problema de seguimiento n.º 13274

Comportamiento anterior

Considere el modelo siguiente:

public class Customer
{
    public int CustomerId { get; set; }
    public ICollection<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
}

Antes de EF Core 3.0, se podía usar la propiedad CustomerId para la clave externa por convención. Pero si Order es un tipo de propiedad, entonces esto convertiría también a CustomerId en la clave principal, algo que no suele ser lo esperado.

Comportamiento nuevo

A partir de la versión 3.0, EF Core no intenta usar las propiedades de claves externas por convención si tienen el mismo nombre que la propiedad de entidad de seguridad. Los patrones de nombre de tipo de entidad de seguridad concatenado con el nombre de propiedad de la entidad de seguridad y de nombre de navegación concatenado con el nombre de propiedad de la entidad de seguridad todavía se hacen coincidir. Por ejemplo:

public class Customer
{
    public int Id { get; set; }
    public ICollection<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
}
public class Customer
{
    public int Id { get; set; }
    public ICollection<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public int BuyerId { get; set; }
    public Customer Buyer { get; set; }
}

Por qué

Este cambio se ha realizado para evitar definir erróneamente una propiedad de clave principal en el tipo de propiedad.

Mitigaciones

Si la propiedad se ha diseñado para ser la clave externa y, por tanto, parte de la clave principal, se debe configurar explícitamente como tal.

La conexión de base de datos ahora se cierra si ya no se usa antes de que se complete TransactionScope

Problema de seguimiento n.º 14218

Comportamiento anterior

Antes de EF Core 3.0, si el contexto abría la conexión dentro de TransactionScope, la conexión permanecía abierta mientras el ámbito actual TransactionScope estuviese activo.

using (new TransactionScope())
{
    using (AdventureWorks context = new AdventureWorks())
    {
        context.ProductCategories.Add(new ProductCategory());
        context.SaveChanges();

        // Old behavior: Connection is still open at this point

        var categories = context.ProductCategories().ToList();
    }
}

Comportamiento nuevo

A partir de la versión 3.0, EF Core cierra la conexión en cuanto se deja de usar.

Por qué

Este cambio permite usar varios contextos en el mismo ámbito TransactionScope. El comportamiento nuevo también coincide con el de EF6.

Mitigaciones

Si la conexión debe permanecer abierta, una llamada explícita a OpenConnection() asegurará que EF Core no la cierre de forma prematura:

using (new TransactionScope())
{
    using (AdventureWorks context = new AdventureWorks())
    {
        context.Database.OpenConnection();
        context.ProductCategories.Add(new ProductCategory());
        context.SaveChanges();

        var categories = context.ProductCategories().ToList();
        context.Database.CloseConnection();
    }
}

Cada propiedad usa la generación de claves enteras en memoria independiente

Problema de seguimiento n.º 6872

Comportamiento anterior

Antes de EF Core 3.0, se usaba un generador de valores compartidos para todas las propiedades de clave entera en memoria.

Comportamiento nuevo

A partir de EF Core 3.0, cada propiedad de clave entera obtiene su propio generador de valores cuando se usa la base de datos en memoria. Además, si se elimina la base de datos, la generación de claves se restablece para todas las tablas.

Por qué

Este cambio se ha realizado para alinear la generación de claves en memoria más estrechamente a la generación de claves de base de datos reales y para mejorar la capacidad para aislar las pruebas entre sí cuando se usa la base de datos en memoria.

Mitigaciones

Esto puede interrumpir una aplicación que se base en el establecimiento de valores de clave específicos en memoria. En su lugar, considere la posibilidad de no depender de valores de clave específicos, o bien de actualizar para que coincida con el comportamiento nuevo.

Los campos de respaldo se usan de forma predeterminada

Problema de seguimiento n.º 12430

Comportamiento anterior

Antes de la versión 3.0, incluso si se conocía el campo de respaldo de una propiedad, de forma predeterminada en EF Core se leía y escribía el valor de propiedad mediante los métodos captadores y establecedores de propiedades. La excepción era la ejecución de consultas, donde el campo de respaldo se establecía directamente si se conocía.

Comportamiento nuevo

A partir de EF Core 3.0, si se conoce el campo de respaldo para una propiedad, EF Core siempre la leerá y escribirá mediante el campo de respaldo. Esto podría provocar una interrupción de la aplicación si depende de un comportamiento adicional codificado en los métodos captadores o establecedores.

Por qué

Este cambio se ha realizado para evitar que EF Core desencadene erróneamente lógica de negocios de forma predeterminada al realizar operaciones de base de datos que implican entidades.

Mitigaciones

El comportamiento anterior a la versión 3.0 se puede restaurar mediante la configuración del modo de acceso de propiedad en ModelBuilder. Por ejemplo:

modelBuilder.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);

Inicio de excepciones si se encuentran varios campos de respaldo compatibles

Problema de seguimiento n.º 12523

Comportamiento anterior

Antes de EF Core 3.0, si varios campos coincidían con las reglas para buscar el campo de respaldo de una propiedad, se elegía un campo según un orden de prioridad. Esto podía producir que, en caso de ambigüedad, se usara el campo incorrecto.

Comportamiento nuevo

A partir de EF Core 3.0, si varios campos coinciden con la misma propiedad, se inicia una excepción.

Por qué

Este cambio se ha realizado para evitar de forma silenciosa el uso de un campo con respecto a otro cuando solo uno puede ser correcto.

Mitigaciones

En las propiedades con campos de respaldo ambiguos se debe especificar de forma explícita el campo que se va usar. Por ejemplo, con la API fluida:

modelBuilder
    .Entity<Blog>()
    .Property(e => e.Id)
    .HasField("_id");

Los nombres de propiedades de solo campo deben coincidir con el nombre del campo

Comportamiento anterior

Antes de EF Core 3.0, una propiedad podía especificarse con un valor de cadena y, si no había ninguna propiedad con ese nombre en el tipo .NET, EF Core intentaba hacerla coincidir con un campo mediante reglas de convención.

private class Blog
{
    private int _id;
    public string Name { get; set; }
}
modelBuilder
    .Entity<Blog>()
    .Property("Id");

Comportamiento nuevo

A partir de EF Core 3.0, una propiedad de solo campo debe coincidir exactamente con el nombre del campo.

modelBuilder
    .Entity<Blog>()
    .Property("_id");

Por qué

Este cambio se realizó para evitar el uso del mismo campo para dos propiedades con nombres similares. También hace que las reglas de coincidencia para propiedades solo de campo sean las mismas que para las propiedades asignadas a propiedades CLR.

Mitigaciones

Las propiedades solo de campo deberían tener el mismo nombre que el campo al que están asignadas. En una próxima versión de EF Core 3.0 tenemos planeado volver a habilitar la configuración explícita de un nombre de campo distinto al nombre de la propiedad (vea el problema n.° 15307):

modelBuilder
    .Entity<Blog>()
    .Property("Id")
    .HasField("_id");

AddDbContext/AddDbContextPool ya no llaman a AddLogging ni a AddMemoryCache

Problema de seguimiento n.° 14756

Comportamiento anterior

Antes de EF Core 3.0, la llamada a AddDbContext o AddDbContextPool también podría registrar los servicios de almacenamiento en caché y de registro con inserción de dependencias a través de llamadas a AddLogging y AddMemoryCache.

Comportamiento nuevo

A partir de EF Core 3.0, AddDbContext y AddDbContextPool ya no registrarán estos servicios con inserción de dependencias (DI).

Por qué

EF Core 3.0 no requiere que estos servicios estén en el contenedor de inserción de dependencias de la aplicación. Pero si ILoggerFactory se registra en el contenedor de DI de la aplicación, EF Core lo empezará a usar de todos modos.

Mitigaciones

Si la aplicación necesita estos servicios, regístrelos de manera explícita con el contenedor de DI mediante AddLogging o AddMemoryCache.

AddEntityFramework* agrega IMemoryCache con un límite de tamaño

Problema de seguimiento n.º 12905

Comportamiento anterior

Antes de EF Core 3.0, la llamada a los métodos AddEntityFramework* también registraba los servicios de almacenamiento en caché de memoria con inserción de dependencias sin límite de tamaño.

Comportamiento nuevo

A partir de EF Core 3.0, AddEntityFramework* registrará un servicio IMemoryCache con un límite de tamaño. Si otros servicios agregados después dependen de IMemoryCache, pueden alcanzar rápidamente el límite predeterminado y provocar excepciones o un rendimiento degradado.

Por qué

El uso de IMemoryCache sin un límite podría dar lugar a un uso de memoria no controlado si hay un error en la lógica de almacenamiento en caché de las consultas o las consultas se generan de forma dinámica. Tener un límite predeterminado mitiga un posible ataque DoS.

Mitigaciones

En la mayoría de los casos, no es necesario llamar a AddEntityFramework* si también se llama a AddDbContext o AddDbContextPool. Por tanto, la mejor mitigación consiste en quitar la llamada a AddEntityFramework*.

Si la aplicación necesita estos servicios, registre de forma explícita una implementación de IMemoryCache con el contenedor de DI por anticipado mediante AddMemoryCache.

DbContext.Entry realiza ahora una operación DetectChanges local

Problema de seguimiento n.º 13552

Comportamiento anterior

Antes de EF Core 3.0, la llamada a DbContext.Entry provocaba que se detectaran cambios para todas las entidades con seguimiento. Esto garantizaba que el estado expuesto en EntityEntry estuviera actualizado.

Comportamiento nuevo

A partir de EF Core 3.0, ahora la llamada a DbContext.Entry solo intenta detectar cambios en la entidad dada y cualquier entidad de seguridad relacionada con ella de la que se haya realizado el seguimiento. Esto significa que es posible que la llamada a este método no haya detectado otros cambios, lo que podría tener implicaciones en el estado de la aplicación.

Observe que si ChangeTracker.AutoDetectChangesEnabled se establece en false incluso esta detección de cambios local se deshabilitará.

Otros métodos que provocan la detección de cambios (como ChangeTracker.Entries y SaveChanges) siguen provocando una acción DetectChanges completa de todas las entidades de las que se realiza el seguimiento.

Por qué

Este cambio se ha realizado para mejorar el rendimiento predeterminado del uso de context.Entry.

Mitigaciones

Llame a ChangeTracker.DetectChanges() de forma explícita antes de llamar a Entry para garantizar el comportamiento anterior a la versión 3.0.

El cliente no genera las claves de matriz de cadena y byte de forma predeterminada

Problema de seguimiento n.º 14617

Comportamiento anterior

Antes de EF Core 3.0, se podían usar las propiedades de clave string y byte[] sin tener que establecer de forma explícita un valor distinto de NULL. En ese caso, el valor de clave se generaba en el cliente como un GUID, que se serializaba en bytes para byte[].

Comportamiento nuevo

A partir de EF Core 3.0, se iniciará una excepción en la que indica que no se ha establecido ningún valor de clave.

Por qué

Este cambio se ha realizado porque los valores string/byte[] generados por el cliente no suelen ser útiles, y el comportamiento predeterminado dificultaba razonar sobre los valores de clave generados de una forma habitual.

Mitigaciones

Se puede obtener el comportamiento anterior a la versión 3.0 si se especifica de forma explícita que las propiedades de clave deben usar los valores generados si no se establece ningún otro valor distinto de NULL. Por ejemplo, con la API fluida:

modelBuilder
    .Entity<Blog>()
    .Property(e => e.Id)
    .ValueGeneratedOnAdd();

O bien con anotaciones de datos:

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }

ILoggerFactory es ahora un servicio con ámbito

Problema de seguimiento n.º 14698

Comportamiento anterior

Antes de EF Core 3.0, ILoggerFactory se registraba como un servicio de singleton.

Comportamiento nuevo

A partir de EF Core 3.0, ILoggerFactory ahora se registra como con ámbito.

Por qué

Este cambio se ha realizado para permitir la asociación de un registrador con una instancia de DbContext, lo que habilita otras funciones y quita algunos casos de comportamiento patológico como un aumento vertiginoso de los proveedores de servicios internos.

Mitigaciones

Este cambio no debería afectar al código de la aplicación a menos que registre y use servicios personalizados en el proveedor de servicios internos de EF Core. Esto no es habitual. En estos casos, la mayoría de los elementos seguirá funcionando, pero cualquier servicio de singleton que dependiera de ILoggerFactory tendrá que cambiarse para obtener la interfaz ILoggerFactory de otra forma.

Si experimenta situaciones como esta, registre un problema en el rastreador de problemas de GitHub de EF Core para hacernos saber cómo usa ILoggerFactory, para que podamos comprender mejor cómo evitar esta interrupción en el futuro.

En los proxies de carga diferida ya no se supone que las propiedades de navegación están totalmente cargadas

Problema de seguimiento n.º 12780

Comportamiento anterior

Antes de EF Core 3.0, una vez que se eliminaba DbContext no había ninguna forma de saber si una determinada propiedad de navegación de una entidad obtenida de ese contexto se había cargado completamente o no. En su lugar, los proxies asumían que una navegación de referencia se cargaba si tenía un valor distinto de NULL, y que una navegación de colección se cargaba si no estaba vacía. En estos casos, el intento de carga diferida era no operativo.

Comportamiento nuevo

A partir de EF Core 3.0, los proxies realizan el seguimiento de si una propiedad de navegación se carga o no. Esto significa que el intento de acceder a una propiedad de navegación que se carga después de que se haya eliminado el contexto siempre será no operativo, incluso cuando la navegación cargada está vacía o es NULL. Por el contrario, el intento de acceder a una propiedad de navegación que no está cargada iniciará una excepción si el contexto se ha eliminado, incluso si la propiedad de navegación es una colección no vacía. Si se produce esta situación, significa que el código de aplicación está intentando usar la carga diferida en un momento no válido y que se debe cambiar la aplicación para que lo no haga.

Por qué

Este cambio se ha realizado para que el comportamiento sea coherente y correcto cuando se intenta la carga diferida de una instancia de DbContext eliminada.

Mitigaciones

Actualice el código de la aplicación para que no intente la carga diferida con un contexto eliminado, o bien configúrelo para que sea no operativo, como se describe en el mensaje de la excepción.

La creación excesiva de proveedores de servicios internos ahora es un error de forma predeterminada

Problema de seguimiento n.º 10236

Comportamiento anterior

Antes de EF Core 3.0, se registraba una advertencia para una aplicación que creaba un número patológico de proveedores de servicios internos.

Comportamiento nuevo

A partir de EF Core 3.0, ahora esta advertencia se considera un error y se inicia una excepción.

Por qué

Este cambio se ha realizado para controlar mejor el código de la aplicación mediante la exposición de este caso patológico de una forma más explícita.

Mitigaciones

Cuando se produce este error, la acción más adecuada consiste en comprender la causa raíz y detener la creación de tantos proveedores de servicios internos. Pero el error se puede convertir en una advertencia (u omitirse) mediante configuración en DbContextOptionsBuilder. Por ejemplo:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .ConfigureWarnings(w => w.Log(CoreEventId.ManyServiceProvidersCreatedWarning));
}

Comportamiento nuevo de HasOne/HasMany llamado con una sola cadena

Problema de seguimiento n.° 9171

Comportamiento anterior

Antes de EF Core 3.0, el código para llamar a HasOne o HasMany con una cadena se interpretaba de manera confusa. Por ejemplo:

modelBuilder.Entity<Samurai>().HasOne("Entrance").WithOne();

El código parece relacionar Samurai con otro tipo de entidad mediante la propiedad de navegación Entrance, que puede ser privada.

En realidad, este código intenta crear una relación con algún tipo de entidad denominada Entrance sin ninguna propiedad de navegación.

Comportamiento nuevo

A partir de EF Core 3.0, el código anterior ahora hace lo que parecía que debía hacer antes.

Por qué

El comportamiento anterior era muy confuso, especialmente al leer el código de configuración y al buscar errores.

Mitigaciones

Esto solo interrumpirá las aplicaciones que configuran de manera explícita las relaciones con cadenas para nombres de tipos y sin especificar explícitamente la propiedad de navegación. Esto no es habitual. El comportamiento anterior se puede obtener al pasar de manera explícita null para el nombre de la propiedad de navegación. Por ejemplo:

modelBuilder.Entity<Samurai>().HasOne("Some.Entity.Type.Name", null).WithOne();

El tipo de valor devuelto para varios métodos asincrónicos se ha cambiado de Task a ValueTask

Problema de seguimiento n.º 15184

Comportamiento anterior

Antes, los siguientes métodos asincrónicos devolvían Task<T>:

  • DbContext.FindAsync()
  • DbSet.FindAsync()
  • DbContext.AddAsync()
  • DbSet.AddAsync()
  • ValueGenerator.NextValueAsync() (y las clases derivadas)

Comportamiento nuevo

Dichos métodos ahora devuelven ValueTask<T> durante el mismo T que antes.

Por qué

Este cambio reduce el número de asignaciones de montones que se producen al invocar estos métodos, lo que mejora el rendimiento general.

Mitigaciones

Las aplicaciones que simplemente esperen las API anteriores solo necesitan recompilarse, sin que sea necesario realizar cambios en el código fuente. Un uso más complejo (p. ej., pasar el valor Task devuelto a Task.WhenAny()) normalmente requiere que el valor ValueTask<T> devuelto se convierta en Task<T> mediante una llamada a AsTask() en él. Tenga en cuenta que esto niega la reducción de asignación que implica este cambio.

La anotación Relational:TypeMapping ahora es simplemente TypeMapping

Problema de seguimiento n.º 9913

Comportamiento anterior

El nombre de anotación para las anotaciones de asignación de tipos era "Relational:TypeMapping".

Comportamiento nuevo

Ahora, el nombre de anotación para las anotaciones de asignación de tipos es "TypeMapping".

Por qué

Ahora, las asignaciones de tipos se usan para algo más que solo para proveedores de bases de datos relacionales.

Mitigaciones

Esto solo interrumpirá a las aplicaciones que acceden directamente a la asignación de tipos como una anotación, lo que no es habitual. La acción más apropiada para corregir es usar la superficie de API para acceder a las asignaciones de tipos en lugar de usar directamente la anotación.

ToTable en un tipo derivado produce una excepción

Problema de seguimiento n.º 11811

Comportamiento anterior

Antes de EF Core 3.0, la llamada a ToTable() en un tipo derivado se omitía, ya que la única estrategia asignación de herencia era TPH, lo que no es válido.

Comportamiento nuevo

A partir de EF Core 3.0, y en preparación para agregar compatibilidad con TPT y TPC en una versión posterior, ahora la llamada a ToTable() en un tipo derivado iniciará una excepción para evitar un cambio de asignación inesperado en el futuro.

Por qué

En la actualidad no se considera válido asignar un tipo derivado a otra tabla. Este cambio evita interrupciones en el futuro, cuando se convierta en una operación válida.

Mitigaciones

Quite todos los intentos de asignar tipos derivados a otras tablas.

ForSqlServerHasIndex se ha reemplazado por HasIndex

Problema de seguimiento n.º 12366

Comportamiento anterior

Antes de EF Core 3.0, ForSqlServerHasIndex().ForSqlServerInclude() proporcionaba una manera de configurar las columnas que se usaban con INCLUDE.

Comportamiento nuevo

A partir de EF Core 3.0, ya se admite el uso de Include en un índice en el nivel relacional. Use HasIndex().ForSqlServerInclude().

Por qué

Este cambio se ha realizado para consolidar la API para índices con Include en un mismo lugar para todos los proveedores de base de datos.

Mitigaciones

Use la API nueva, como se ha mostrado anteriormente.

Cambios en la API de metadatos

Problema de seguimiento n.º 214

Comportamiento nuevo

Las siguientes propiedades se han convertido en métodos de extensión:

  • IEntityType.QueryFilter ->GetQueryFilter()
  • IEntityType.DefiningQuery ->GetDefiningQuery()
  • IProperty.IsShadowProperty ->IsShadowProperty()
  • IProperty.BeforeSaveBehavior ->GetBeforeSaveBehavior()
  • IProperty.AfterSaveBehavior ->GetAfterSaveBehavior()

Por qué

Este cambio simplifica la implementación de las interfaces mencionadas anteriormente.

Mitigaciones

Use los nuevos métodos de extensión.

Cambios en la API de metadatos específicos del proveedor

Problema de seguimiento n.º 214

Comportamiento nuevo

Los métodos de extensión específicos del proveedor se simplificarán:

  • IProperty.Relational().ColumnName ->IProperty.GetColumnName()
  • IEntityType.SqlServer().IsMemoryOptimized ->IEntityType.IsMemoryOptimized()
  • PropertyBuilder.UseSqlServerIdentityColumn() ->PropertyBuilder.UseIdentityColumn()

Por qué

Este cambio simplifica la implementación de los métodos de extensión mencionados anteriormente.

Mitigaciones

Use los nuevos métodos de extensión.

EF Core ya no envía pragma para el cumplimiento de SQLite FK

Problema de seguimiento n.º 12151

Comportamiento anterior

Antes de EF Core 3.0, EF Core enviaba PRAGMA foreign_keys = 1 cuando se abría una conexión con SQLite.

Comportamiento nuevo

A partir de EF Core 3.0, EF Core ya no envía PRAGMA foreign_keys = 1 cuando se abre una conexión con SQLite.

Por qué

Este cambio se ha realizado porque en EF Core se usa SQLitePCLRaw.bundle_e_sqlite3 de forma predeterminada, lo que a su vez significa que el cumplimiento de CD está activado de forma predeterminada y no es necesario habilitarlo explícitamente cada vez que se abra una conexión.

Mitigaciones

Las claves externas se habilitan de forma predeterminada en SQLitePCLRaw.bundle_e_sqlite3, que en EF Core se usa de forma predeterminada. Para otros casos, las claves externas se pueden habilitar mediante la especificación de Foreign Keys=True en la cadena de conexión.

Microsoft.EntityFrameworkCore.Sqlite ahora depende de SQLitePCLRaw.bundle_e_sqlite3

Comportamiento anterior

Antes de EF Core 3.0, en EF Core se usaba SQLitePCLRaw.bundle_green.

Comportamiento nuevo

A partir de EF Core 3.0, en EF Core se usa SQLitePCLRaw.bundle_e_sqlite3.

Por qué

Este cambio se ha realizado para que la versión de SQLite que se usa en iOS sea coherente con otras plataformas.

Mitigaciones

Para usar la versión nativa de SQLite en iOS, configure Microsoft.Data.Sqlite para usar otra agrupación SQLitePCLRaw.

Los valores GUID se almacenan ahora como TEXT en SQLite

Problema de seguimiento n.º 15078

Comportamiento anterior

Antes, los valores GUID se almacenaban como valores BLOB en SQLite.

Comportamiento nuevo

Ahora, los valores GUID se almacenan como TEXT.

Por qué

El formato binario de los GUID no está normalizado. El almacenamiento de los valores como TEXT mejora la compatibilidad de la base de datos con otras tecnologías.

Mitigaciones

Puede migrar las bases de datos existentes al nuevo formato ejecutando SQL de la siguiente forma.

UPDATE MyTable
SET GuidColumn = hex(substr(GuidColumn, 4, 1)) ||
                 hex(substr(GuidColumn, 3, 1)) ||
                 hex(substr(GuidColumn, 2, 1)) ||
                 hex(substr(GuidColumn, 1, 1)) || '-' ||
                 hex(substr(GuidColumn, 6, 1)) ||
                 hex(substr(GuidColumn, 5, 1)) || '-' ||
                 hex(substr(GuidColumn, 8, 1)) ||
                 hex(substr(GuidColumn, 7, 1)) || '-' ||
                 hex(substr(GuidColumn, 9, 2)) || '-' ||
                 hex(substr(GuidColumn, 11, 6))
WHERE typeof(GuidColumn) == 'blob';

En EF Core, también puede seguir usando el comportamiento anterior configurando un convertidor de valores en estas propiedades.

modelBuilder
    .Entity<MyEntity>()
    .Property(e => e.GuidProperty)
    .HasConversion(
        g => g.ToByteArray(),
        b => new Guid(b));

Microsoft.Data.Sqlite sigue siendo capaz de leer valores GUID de ambas columnas BLOB y TEXT. Sin embargo, dado que el formato predeterminado de los parámetros y las constantes ha cambiado, seguramente deberá realizar alguna acción en la mayoría de casos que impliquen el uso de valores GUID.

Los valores char se almacenan ahora como TEXT en SQLite

Problema de seguimiento n.º 15020

Comportamiento anterior

Anteriormente los valores char se almacenaban como valores INTEGER en SQLite. Por ejemplo, un valor char de A se almacenaba como el valor entero 65.

Comportamiento nuevo

Ahora, los valores char se almacenan como TEXT.

Por qué

El almacenamiento de valores como TEXT es más natural y mejora la compatibilidad de la base de datos con otras tecnologías.

Mitigaciones

Puede migrar las bases de datos existentes al nuevo formato ejecutando SQL de la siguiente forma.

UPDATE MyTable
SET CharColumn = char(CharColumn)
WHERE typeof(CharColumn) = 'integer';

En EF Core, también puede seguir usando el comportamiento anterior configurando un convertidor de valores en estas propiedades.

modelBuilder
    .Entity<MyEntity>()
    .Property(e => e.CharProperty)
    .HasConversion(
        c => (long)c,
        i => (char)i);

Microsoft.Data.Sqlite también puede leer valores de caracteres tanto de columnas INTEGER como de columnas TEXT, por lo que es posible que no deba hacer nada dependiendo de su caso.

Los id. de migración ahora se generan usando el calendario de la referencia cultural invariable

Problema de seguimiento n.º 12978

Comportamiento anterior

Los identificadores de migración se generaban de forma involuntaria con el calendario de la referencia cultural actual.

Comportamiento nuevo

Ahora los id. de migración siempre se generan usando el calendario de la referencia cultural invariable (gregoriano).

Por qué

El orden de las migraciones es importante al actualizar la base de datos o al solucionar conflictos de combinación. Al usar el calendario invariable, se evitan problemas de ordenación que pueden producirse si los miembros del equipo tienen distintos calendarios del sistema.

Mitigaciones

Esta cambio afecta a todas las personas que usan un calendario no gregoriano en el que el año sea superior al del calendario gregoriano (como el calendario budista tailandés). Los id. de migración existentes deberán actualizarse para que las migraciones nuevas se ordenen después de las existentes.

Puede ver el id. de migración en el atributo Migration de los archivos de diseñador de la migración.

 [DbContext(typeof(MyDbContext))]
-[Migration("25620318122820_MyMigration")]
+[Migration("20190318122820_MyMigration")]
 partial class MyMigration
 {

También debe actualizarse la tabla de historial de migraciones.

UPDATE __EFMigrationsHistory
SET MigrationId = CONCAT(LEFT(MigrationId, 4)  - 543, SUBSTRING(MigrationId, 4, 150))

Se ha quitado el elemento UseRowNumberForPaging

Problema de seguimiento n.º 16400

Comportamiento anterior

Antes de EF Core 3.0, UseRowNumberForPaging se podía usar para generar SQL para la paginación de forma que fuera compatible con SQL Server 2008.

Comportamiento nuevo

A partir de EF Core 3.0, EF solo genera SQL para la paginación que únicamente es compatible con las versiones posteriores de SQL Server.

Por qué

El motivo de este cambio es que SQL Server 2008 ya no se admite. Además, la actualización de esta característica para que funcionase con los cambios en las consultas implementados en EF Core 3.0 llevaría mucho trabajo.

Mitigaciones

Se recomienda actualizar a una versión más reciente de SQL Server, o bien utilizar un nivel de compatibilidad superior, de modo que el SQL que se genere se admita. Dicho esto, si no puede hacerlo, escriba un comentario en el problema de seguimiento con los detalles al respecto. En función de los comentarios, es posible que volvamos a valorar esta decisión.

La información o los metadatos de la extensión se han quitado de IDbContextOptionsExtension

Problema de seguimiento n.º 16119

Comportamiento anterior

IDbContextOptionsExtension incluía métodos para proporcionar metadatos sobre la extensión.

Comportamiento nuevo

Estos métodos se han movido a una nueva clase base abstracta DbContextOptionsExtensionInfo, que se devuelve desde una nueva propiedad IDbContextOptionsExtension.Info.

Por qué

Al lanzarse las versiones 2.0 y 3.0, tuvimos que agregar o cambiar estos métodos varias veces. Su división en una nueva clase base abstracta facilitará la realización de este tipo de cambios sin interrumpir las extensiones existentes.

Mitigaciones

Actualice las extensiones para seguir el nuevo patrón. Encontrará ejemplos en las muchas implementaciones de IDbContextOptionsExtension para los diferentes tipos de extensiones en el código fuente de EF Core.

LogQueryPossibleExceptionWithAggregateOperator ha cambiado de nombre

Problema de seguimiento n.º 10985

Change

Se ha cambiado el nombre de RelationalEventId.LogQueryPossibleExceptionWithAggregateOperator a RelationalEventId.LogQueryPossibleExceptionWithAggregateOperatorWarning.

Por qué

Conviene alinear el nombre de este evento de advertencia con el del resto de eventos de advertencia.

Mitigaciones

Use el nuevo nombre. (Tenga en cuenta que el número de id. evento sigue siendo el mismo).

Clarificación de la API para nombres de restricciones de claves externas

Problema de seguimiento n.º 10730

Comportamiento anterior

Antes de EF Core 3.0, se utilizaba simplemente el término "nombre" para hacer referencia a los nombres de las restricciones de claves externas. Por ejemplo:

var constraintName = myForeignKey.Name;

Comportamiento nuevo

A partir de EF Core 3.0, el término con el que se hace referencia a los nombres de las restricciones de claves externas es "nombre de la restricción". Por ejemplo:

var constraintName = myForeignKey.ConstraintName;

Por qué

Este cambio permite mejorar la coherencia relativa a la nomenclatura en este aspecto y aclarar que se trata del nombre de una restricción de clave externa, y no del de la columna o propiedad en la que está definida la clave externa.

Mitigaciones

Use el nuevo nombre.

IRelationalDatabaseCreator.HasTables/HasTablesAsync se han hecho públicos

Problema de seguimiento n.° 15997

Comportamiento anterior

Antes de EF Core 3.0, estos métodos estaban protegidos.

Comportamiento nuevo

Desde EF Core 3.0, estos métodos son públicos.

Por qué

EF usa estos métodos para determinar si se ha creado una base de datos, pero está vacía. Esto también puede resultar útil fuera de EF al determinar si se deben aplicar migraciones o no.

Mitigaciones

Cambie la accesibilidad de cualquier invalidación.

Microsoft.EntityFrameworkCore.Design es ahora un paquete DevelopmentDependency

Problema de seguimiento n.° 11506

Comportamiento anterior

Antes de EF Core 3.0, Microsoft.EntityFrameworkCore.Design era un paquete NuGet regular con un ensamblado al que podían hacer referencia los proyectos que dependían de él.

Comportamiento nuevo

Desde EF Core 3.0, es un paquete DevelopmentDependency. Esto significa que la dependencia no fluirá de manera transitiva en otros proyectos y que ya no puede, de forma predeterminada, hacer referencia a su ensamblado.

Por qué

Este paquete solo está destinado a usarse en tiempo de diseño. Las aplicaciones implementadas no deben hacer referencia al mismo. Hacer que el paquete sea DevelopmentDependency refuerza esta recomendación.

Mitigaciones

Si tiene que hacer referencia a este paquete para invalidar el comportamiento en tiempo de diseño de EF Core, puede actualizar los metadatos de elementos PackageReference del proyecto.

<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.0.0">
  <PrivateAssets>all</PrivateAssets>
  <!-- Remove IncludeAssets to allow compiling against the assembly -->
  <!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
</PackageReference>

Si se hace referencia al paquete de manera transitiva a través de Microsoft.EntityFrameworkCore.Tools, tendrá que agregar una PackageReference explícita al paquete para cambiar sus metadatos. Este tipo de referencia explícita debe agregarse a cualquier proyecto que requiera los tipos de paquete.

SQLitePCL.raw se ha actualizado a la versión 2.0.0

Problema de seguimiento n.° 14824

Comportamiento anterior

Microsoft.EntityFrameworkCore.Sqlite dependía anteriormente de la versión 1.1.12 de SQLitePCL.raw.

Comportamiento nuevo

Hemos actualizado nuestro paquete para depender de la versión 2.0.0.

Por qué

La versión 2.0.0 de SQLitePCL.raw selecciona .NET Standard 2.0 como destino. Anteriormente seleccionaba .NET Standard 1.1 como destino, que requería el cierre a gran escala de paquetes transitivos para su funcionamiento.

Mitigaciones

En la versión 2.0.0 de SQLitePCL.raw se incluyen algunos cambios importantes. Consulte las notas de la versión para obtener detalles.

NetTopologySuite se actualizó a la versión 2.0.0

Problema de seguimiento n.° 14825

Comportamiento anterior

Los paquetes espaciales anteriormente dependían de la versión 1.15.1 de NetTopologySuite.

Comportamiento nuevo

Hemos actualizado nuestro paquete para depender de la versión 2.0.0.

Por qué

La versión 2.0.0 de NetTopologySuite pretende resolver varios problemas de usabilidad que encontraron los usuarios de EF Core.

Mitigaciones

En la versión 2.0.0 de NetTopologySuite se incluyen algunos cambios importantes. Consulte las notas de la versión para obtener detalles.

Se usa Microsoft.Data.SqlClient en lugar de System.Data.SqlClient

Problema de seguimiento n.º 15636

Comportamiento anterior

Microsoft.EntityFrameworkCore.SqlServer dependía anteriormente de la versión System.Data.SqlClient.

Comportamiento nuevo

Hemos actualizado nuestro paquete para que dependa de Microsoft.Data.SqlClient.

Por qué

A partir de ahora, Microsoft.Data.SqlClient es el controlador de acceso a datos insignia para SQL Server y System.Data.SqlClient ya no es el centro de desarrollo. Algunas características importantes, como Always Encrypted, solo están disponibles en Microsoft.Data.SqlClient.

Mitigaciones

Si el código toma una dependencia directa en System.Data.SqlClient, debe cambiarla para que haga referencia a Microsoft.Data.SqlClient en su lugar. Dado que los dos paquetes mantienen un grado muy alto de compatibilidad con la API, solo debería ser un paquete simple y un cambio de espacio de nombres.

Se deben configurar varias relaciones de referencia automática ambiguas

Problema de seguimiento n.º 13573

Comportamiento anterior

Un tipo de entidad con varias propiedades de navegación unidireccional de referencia automática y claves externas coincidentes se configuró incorrectamente como una única relación. Por ejemplo:

public class User
{
        public Guid Id { get; set; }
        public User CreatedBy { get; set; }
        public User UpdatedBy { get; set; }
        public Guid CreatedById { get; set; }
        public Guid? UpdatedById { get; set; }
}

Comportamiento nuevo

Este escenario se detecta ahora en la generación del modelo y se produce una excepción que indica que el modelo es ambiguo.

Por qué

El modelo resultante era ambiguo, y lo más probable es que sea incorrecto en este caso.

Mitigaciones

Utilice la configuración completa de la relación. Por ejemplo:

modelBuilder
     .Entity<User>()
     .HasOne(e => e.CreatedBy)
     .WithMany();

 modelBuilder
     .Entity<User>()
     .HasOne(e => e.UpdatedBy)
     .WithMany();

DbFunction.Schema es NULL o la cadena vacía lo configura para estar en el esquema predeterminado del modelo

Problema de seguimiento n.º 12757

Comportamiento anterior

Una función DbFunction configurada con el esquema como una cadena vacía se trataba como una función integrada sin un esquema. Por ejemplo, el código siguiente asignará la función CLR DatePart a la función integrada DATEPART en SqlServer.

[DbFunction("DATEPART", Schema = "")]
public static int? DatePart(string datePartArg, DateTime? date) => throw new Exception();

Comportamiento nuevo

Todas las asignaciones de DbFunction se consideran asignadas a funciones definidas por el usuario. Por lo tanto, el valor de cadena vacía colocaría la función dentro del esquema predeterminado del modelo, que podría ser el esquema configurado de forma explícita mediante modelBuilder.HasDefaultSchema() de la API fluida o dbo en caso contrario.

Por qué

Anteriormente, el esquema vacío era una manera de indicar que la función estaba integrada, pero esa lógica solo es aplicable a SqlServer, donde las funciones integradas no pertenecen a ningún esquema.

Mitigaciones

Configure la traslación de DbFunction manualmente para asignarla a una función integrada.

modelBuilder
    .HasDbFunction(typeof(MyContext).GetMethod(nameof(MyContext.DatePart)))
    .HasTranslation(args => SqlFunctionExpression.Create("DatePart", args, typeof(int?), null));

EF Core 3.0 tiene como destino .NET Standard 2.1, y no .NET Standard 2.0 Revertido

Problema de seguimiento n.º 15498

EF Core 3.0 tiene como destino .NET Standard 2.1, que es un cambio importante que excluye las aplicaciones de .NET Framework. En EF Core 3.1 se revirtió esto y ahora tiene como destino .NET Standard 2.0 de nuevo.

La ejecución de consultas se registra en el nivel de depuración Revertido

Problema de seguimiento n.º 14523

Revertimos este cambio porque la nueva configuración de EF Core 3.0 permite a la aplicación especificar el nivel de registro para cualquier evento. Por ejemplo, para cambiar el registro de SQL a Debug, configure el nivel de forma explícita en OnConfiguring o AddDbContext:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlServer(connectionString)
        .ConfigureWarnings(c => c.Log((RelationalEventId.CommandExecuting, LogLevel.Debug)));