Nuevas características de EF Core 2.0

.NET Standard 2.0

EF Core ahora tiene como destino .NET Standard 2.0, lo que significa que puede funcionar con .NET Core 2.0, .NET Framework 4.6.1 y otras bibliotecas que implementan .NET Standard 2.0. Consulte Implementaciones de .NET compatibles para obtener más información sobre lo que se admite.

Modeling

Fragmentación de tablas

Ahora es posible asignar dos o más tipos de entidad a la misma tabla donde se compartirán las columnas de clave principal y cada fila corresponderá a dos o más entidades.

Para utilizar el particionamiento de tablas, debe configurarse una relación identificadora (en la que las propiedades de la clave externa forman la clave principal) entre todos los tipos de entidades que comparten la tabla.

modelBuilder.Entity<Product>()
    .HasOne(e => e.Details).WithOne(e => e.Product)
    .HasForeignKey<ProductDetails>(e => e.Id);
modelBuilder.Entity<Product>().ToTable("Products");
modelBuilder.Entity<ProductDetails>().ToTable("Products");

Lea la sección sobre la división de tablas para obtener más información sobre esta característica.

Tipos de propiedad

Un tipo de entidad poseída puede compartir el mismo tipo de .NET con otro tipo de entidad poseída, pero dado que no puede identificarse solo por el tipo de .NET, debe haber otra navegación hacia él desde otro tipo de entidad. La entidad que contiene una navegación definitoria actúa como propietario. Al consultar al propietario, los tipos de propiedad se incluyen de forma predeterminada.

Por convención, se creará una clave principal oculta para el tipo poseído y se asignará a la misma tabla que el propietario mediante la división de tablas. Esto permite usar tipos de propiedad de forma similar a cómo se usan los tipos complejos en EF6:

modelBuilder.Entity<Order>().OwnsOne(p => p.OrderDetails, cb =>
    {
        cb.OwnsOne(c => c.BillingAddress);
        cb.OwnsOne(c => c.ShippingAddress);
    });

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

public class OrderDetails
{
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

public class StreetAddress
{
    public string Street { get; set; }
    public string City { get; set; }
}

Lea la sección sobre los tipos de entidad propiedad para obtener más información sobre esta característica.

Filtros de consulta de nivel de modelo

EF Core 2.0 incluye una nueva característica que llamamos filtros de consulta de nivel de modelo. Esta característica permite definir predicados de consulta LINQ (una expresión booleana que normalmente se pasa al operador de consulta LINQ Where) directamente en tipos de entidad en el modelo de metadatos (normalmente en OnModelCreating). Estos filtros se aplican automáticamente a cualquier consulta LINQ que implique esos Tipos de Entidad, incluidos los Tipos de Entidad a los que se hace referencia indirectamente, como mediante el uso de Include o referencias directas a propiedades de navegación. Algunas aplicaciones comunes de esta característica son:

  • Eliminación temporal: un tipo de entidad define una propiedad IsDeleted.
  • Multitenencia: Un tipo de entidad define una propiedad TenantId.

Este es un ejemplo sencillo que muestra la característica para los dos escenarios enumerados anteriormente:

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public int TenantId { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>().HasQueryFilter(
            p => !p.IsDeleted
            && p.TenantId == this.TenantId);
    }
}

Definimos un filtro de nivel de modelo que implementa multitenencia y eliminación temporal para instancias del tipo de entidad Post. Anote el uso de una DbContext propiedad de nivel de instancia: TenantId. Los filtros de nivel de modelo usarán el valor de la instancia de contexto correcta (es decir, la instancia de contexto que ejecuta la consulta).

Los filtros se pueden deshabilitar para consultas LINQ individuales mediante el operador IgnoreQueryFilters().

Limitaciones

  • No se permiten referencias de navegación. Esta característica se puede agregar en función de los comentarios.
  • Los filtros solo se pueden definir en el tipo de entidad raíz de una jerarquía.

Asignación de funciones escalares de base de datos

EF Core 2.0 incluye una contribución importante de Paul Middleton que permite asignar funciones escalares de base de datos a códigos auxiliares para que se puedan usar en consultas LINQ y traducirlas a SQL.

Esta es una breve descripción de cómo se puede usar la característica:

Declare un método estático en DbContext y anote con DbFunctionAttribute:

public class BloggingContext : DbContext
{
    [DbFunction]
    public static int PostReadCount(int blogId)
    {
        throw new NotImplementedException();
    }
}

Los métodos como este se registran automáticamente. Una vez registradas, las llamadas al método de una consulta LINQ se pueden traducir a llamadas de función en SQL:

var query =
    from p in context.Posts
    where BloggingContext.PostReadCount(p.Id) > 5
    select p;

Cosas a tener en cuenta:

  • Por convención, el nombre del método se usa como nombre de una función (en este caso, una función definida por usuario) al generar SQL, pero puede sobrescribir el nombre y el esquema durante el registro del método.
  • Actualmente solo se admiten funciones escalares.
  • Debe crear la función mapeada en la base de datos. Las migraciones de EF Core no se encargarán de crear esto.

Configuración de tipos independientes para el código en primer lugar

En EF6 se pudo encapsular la primera configuración del código de un tipo de entidad específico derivando de EntityTypeConfiguration. En EF Core 2.0, vamos a devolver este patrón:

class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
    public void Configure(EntityTypeBuilder<Customer> builder)
    {
        builder.HasKey(c => c.AlternateKey);
        builder.Property(c => c.Name).HasMaxLength(200);
    }
}

...
// OnModelCreating
builder.ApplyConfiguration(new CustomerConfiguration());

Alto rendimiento

Agrupación de DbContext

El patrón básico para usar EF Core en una aplicación de ASP.NET Core normalmente implica registrar un tipo DbContext personalizado en el sistema de inserción de dependencias y obtener instancias de ese tipo a través de parámetros de constructor en controladores. Esto significa que se crea una nueva instancia de DbContext para cada solicitud.

En la versión 2.0, presentamos una nueva manera de registrar tipos dbContext personalizados en la inserción de dependencias que introduce de forma transparente un grupo de instancias de DbContext reutilizables. Para usar la agrupación de DbContext, utilice AddDbContextPool en lugar de AddDbContext durante el registro del servicio.

services.AddDbContextPool<BloggingContext>(
    options => options.UseSqlServer(connectionString));

Si se usa este método, en el momento en que un controlador solicita una instancia de DbContext, primero comprobaremos si hay una instancia disponible en el grupo. Una vez finalizado el procesamiento de solicitudes, cualquier estado de la instancia se restablece y la instancia se devuelve al grupo.

Esto es conceptualmente similar a cómo funciona la agrupación de conexiones en proveedores de ADO.NET y tiene la ventaja de ahorrar parte del costo de inicialización de la instancia de DbContext.

Limitaciones

El nuevo método presenta algunas limitaciones sobre lo que se puede hacer en el OnConfiguring() método de DbContext.

Advertencia

Evite usar la agrupación de DbContext si mantiene su propio estado (por ejemplo, campos privados) en su clase DbContext derivada, ya que este no debería compartirse entre diferentes solicitudes. EF Core solo restablecerá el estado que conoce antes de agregar una instancia de DbContext al grupo.

Consultas compiladas explícitamente

Esta es la segunda función de rendimiento a la que se puede optar, diseñada para ofrecer ventajas en escenarios a gran escala.

Las API de consulta compiladas manual o explícitamente están disponibles en versiones anteriores de EF y también en LINQ to SQL para permitir que las aplicaciones almacenen en caché la traducción de consultas para que se puedan calcular solo una vez y ejecutarse muchas veces.

Aunque en general EF Core puede compilar y almacenar en caché automáticamente consultas basadas en una representación hash de las expresiones de consulta, este mecanismo se puede usar para obtener una pequeña ganancia de rendimiento pasando el cálculo del hash y la búsqueda de caché, lo que permite a la aplicación usar una consulta ya compilada a través de la invocación de un delegado.

// Create an explicitly compiled query
private static Func<CustomerContext, int, Customer> _customerById =
    EF.CompileQuery((CustomerContext db, int id) =>
        db.Customers
            .Include(c => c.Address)
            .Single(c => c.Id == id));

// Use the compiled query by invoking it
using (var db = new CustomerContext())
{
   var customer = _customerById(db, 147);
}

Change Tracking

Attach puede monitorizar un gráfico de entidades nuevas y existentes.

EF Core admite la generación automática de valores de clave a través de diversos mecanismos. Al usar esta característica, se genera un valor si la propiedad de clave es el valor predeterminado de CLR, normalmente cero o null. Esto significa que un gráfico de entidades se puede pasar a DbContext.Attach o DbSet.Attach y EF Core marcará esas entidades que ya tienen una clave establecida como Unchanged mientras que esas entidades que no tienen un conjunto de claves se marcarán como Added. Esto facilita adjuntar un gráfico que combina entidades nuevas y existentes al usar claves generadas. DbContext.Update y DbSet.Update funcionan de la misma manera, excepto que las entidades con un conjunto de claves se marcan como Modified en lugar de Unchanged.

Query

Traducción mejorada de LINQ

Permite que se ejecuten correctamente más consultas, con más lógica que se evalúa en la base de datos (en lugar de en memoria) y menos datos innecesariamente recuperados de la base de datos.

Mejoras de GroupJoin

Este trabajo mejora el código SQL que se genera para las combinaciones en grupo. Las combinaciones de grupo a menudo son el resultado de subconsultas en propiedades de navegación opcionales.

Interpolación de cadenas en FromSql y ExecuteSqlCommand

C# 6 introdujo la interpolación de cadenas, una característica que permite que las expresiones de C# se inserte directamente en literales de cadena, lo que proporciona una buena manera de crear cadenas en tiempo de ejecución. En EF Core 2.0 hemos agregado compatibilidad especial con cadenas interpoladas a nuestras dos API principales que aceptan cadenas SQL sin formato: FromSql y ExecuteSqlCommand. Esta nueva compatibilidad permite usar la interpolación de cadenas de C# de forma "segura". Es decir, de una manera que protege frente a errores comunes de inyección de CÓDIGO SQL que pueden producirse al construir SQL dinámicamente en tiempo de ejecución.

Este es un ejemplo:

var city = "London";
var contactTitle = "Sales Representative";

using (var context = CreateContext())
{
    context.Set<Customer>()
        .FromSql($@"
            SELECT *
            FROM ""Customers""
            WHERE ""City"" = {city} AND
                ""ContactTitle"" = {contactTitle}")
            .ToArray();
  }

En este ejemplo, hay dos variables incrustadas en la cadena de formato SQL. EF Core generará el siguiente código SQL:

@p0='London' (Size = 4000)
@p1='Sales Representative' (Size = 4000)

SELECT *
FROM ""Customers""
WHERE ""City"" = @p0
    AND ""ContactTitle"" = @p1

EF.Functions.Like()

Hemos agregado la propiedad EF.Functions, que pueden utilizar EF Core o los proveedores para definir métodos que se mapean a funciones o operadores de base de datos, de manera que se puedan invocar en consultas LINQ. El primer ejemplo de este método es Like():

var aCustomers =
    from c in context.Customers
    where EF.Functions.Like(c.Name, "a%")
    select c;

Tenga en cuenta que Like() incluye una implementación en memoria, que puede ser útil al trabajar con una base de datos en memoria o cuando la evaluación del predicado debe producirse en el lado cliente.

Administración de bases de datos

Enlace de pluralización para el andamiaje de DbContext

EF Core 2.0 presenta un nuevo servicio IPluralizer que se usa para singularizar nombres de tipo de entidad y pluralizar nombres dbSet. La implementación predeterminada es un no-op, por lo que se trata de un enlace en el que los usuarios pueden conectar fácilmente su propio pluralizador.

Aquí se muestra cómo un desarrollador puede integrar su propio pluralizador.

public class MyDesignTimeServices : IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection services)
    {
        services.AddSingleton<IPluralizer, MyPluralizer>();
    }
}

public class MyPluralizer : IPluralizer
{
    public string Pluralize(string name)
    {
        return Inflector.Inflector.Pluralize(name) ?? name;
    }

    public string Singularize(string name)
    {
        return Inflector.Inflector.Singularize(name) ?? name;
    }
}

Otros

Mover el proveedor ADO.NET de SQLite a SQLitePCL.raw

Esto nos proporciona una solución más sólida en Microsoft.Data.Sqlite para distribuir archivos binarios nativos de SQLite en distintas plataformas.

Solo un proveedor por modelo

Aumenta significativamente cómo los proveedores pueden interactuar con el modelo y simplifica cómo funcionan las convenciones, las anotaciones y las API fluidas con diferentes proveedores.

EF Core 2.0 ahora compilará un IModel diferente para cada proveedor que se use. Esto suele ser transparente para la aplicación. Esto ha facilitado una simplificación de las API de metadatos de nivel inferior, de modo que cualquier acceso a los conceptos comunes de metadatos relacionales siempre se realiza a través de una llamada a .Relational en lugar de .SqlServer, .Sqlite, etc.

Registro y diagnóstico consolidados

Los mecanismos de registro (basados en ILogger) y Diagnóstico (basado en DiagnosticSource) ahora comparten más código.

Los identificadores de evento de los mensajes enviados a un ILogger han cambiado en la versión 2.0. Los identificadores de evento ahora son únicos en el código de EF Core. Estos mensajes también siguen el patrón estándar para el registro estructurado usado por, por ejemplo, MVC.

Las categorías del registrador también han cambiado. Ahora hay un conjunto conocido de categorías a las que se accede a través de DbLoggerCategory.

Los eventos DiagnosticSource ahora usan los mismos nombres de identificador de evento que los mensajes correspondientes ILogger .