Índices

Los índices son un concepto común en muchos almacenes de datos. Si bien su implementación en el almacén de datos puede variar, se usan para hacer que las búsquedas basadas en una columna (o en un conjunto de columnas) sean más eficaces. Consulte la sección de índices de la documentación de rendimiento para obtener más información sobre el uso adecuado de los índices.

Puede especificar un índice sobre una columna de la forma siguiente:

[Index(nameof(Url))]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Nota:

Por convención, se crea un índice en cada propiedad (o conjunto de propiedades) que se usa como clave externa.

Índices compuestos

Un índice también puede abarcar más de una columna:

[Index(nameof(FirstName), nameof(LastName))]
public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Los índices de varias columnas, también denominados índices compuestos, aceleran las consultas que filtran por las columnas del índice, pero también las consultas que solo filtran por las primeras columnas que cubre el índice. Para obtener más información, consulte la documentación de rendimiento.

Unicidad del índice

De forma predeterminada, los índices no son únicos: se permite que varias filas tengan los mismos valores para el conjunto de columnas del índice. Puede hacer que un índice sea único de la siguiente manera:

[Index(nameof(Url), IsUnique = true)]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Si se intenta insertar más de una entidad con los mismos valores para el conjunto de columnas del índice, se producirá una excepción.

Criterio de ordenación de índices

Nota:

Esta característica se incluye por primera vez en EF Core 7.0.

En la mayoría de las bases de datos, cada columna cubierta por un índice puede ser ascendente o descendente. En el caso de los índices que abarcan solo una columna, esto normalmente no importa: la base de datos puede recorrer el índice en orden inverso según sea necesario. Sin embargo, para los índices compuestos, la ordenación puede ser fundamental para un buen rendimiento y puede significar la diferencia entre un índice que se usa en una consulta o no. En general, los criterios de ordenación de las columnas de índice deben corresponder a los especificados en la cláusula ORDER BY de la consulta.

El criterio de ordenación del índice es ascendente de forma predeterminada. Puede hacer que todas las columnas tengan un orden descendente de la siguiente manera:

[Index(nameof(Url), nameof(Rating), AllDescending = true)]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    public int Rating { get; set; }
}

También puede especificar el criterio de ordenación columna por columna de la siguiente manera:

[Index(nameof(Url), nameof(Rating), IsDescending = new[] { false, true })]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    public int Rating { get; set; }
}

Nomenclatura de índices y varios índices

Por convención, los índices creados en una base de datos relacional se denominan IX_<type name>_<property name>. En cuanto a los índices compuestos, <property name> se convierte en una lista de nombres de propiedad separados por caracteres de subrayado.

Puede establecer el nombre del índice creado en la base de datos:

[Index(nameof(Url), Name = "Index_Url")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Tenga en cuenta que si llama a HasIndex más de una vez en el mismo conjunto de propiedades, sigue configurando un único índice en lugar de crear uno nuevo:

modelBuilder.Entity<Blog>()
    .HasIndex(b => new { b.FirstName, b.LastName })
    .HasDatabaseName("IX_Names_Ascending");

modelBuilder.Entity<Blog>()
    .HasIndex(b => new { b.FirstName, b.LastName })
    .HasDatabaseName("IX_Names_Descending")
    .IsDescending();

Dado que la segunda llamada a HasIndex invalida la primera, esto crea solo un índice único descendente. Esto puede ser útil para configurar aún más un índice creado por convención.

Para crear varios índices en el mismo conjunto de propiedades, pase un nombre a HasIndex, que se usará para identificar el índice en el modelo de EF y para distinguirlo de otros índices en las mismas propiedades:

modelBuilder.Entity<Blog>()
    .HasIndex(b => new { b.FirstName, b.LastName }, "IX_Names_Ascending");

modelBuilder.Entity<Blog>()
    .HasIndex(b => new { b.FirstName, b.LastName }, "IX_Names_Descending")
    .IsDescending();

Tenga en cuenta que este nombre también se usa como valor predeterminado para el nombre de la base de datos, por lo que no es necesario llamar a HasDatabaseName explícitamente.

Filtro de índice

Algunas bases de datos relacionales permiten especificar un índice filtrado o parcial. Esto le permite indexar solo un subconjunto de los valores de una columna, lo que reduce el tamaño del índice y mejora tanto el rendimiento como el uso del espacio en disco. Para obtener más información sobre los índices filtrados de SQL Server, consulte la documentación.

Puede usar la API Fluent para especificar un filtro en un índice, que se proporciona como una expresión SQL:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasIndex(b => b.Url)
        .HasFilter("[Url] IS NOT NULL");
}

Al usar el proveedor SQL Server, EF agrega un filtro 'IS NOT NULL' para todas las columnas que aceptan valores NULL que forman parte de un índice único. Para invalidar esta convención, puede proporcionar un valor null.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasIndex(b => b.Url)
        .IsUnique()
        .HasFilter(null);
}

Columnas incluidas

Algunas bases de datos relacionales permiten configurar un conjunto de columnas que se incluyen en el índice, pero que no forman parte de su "clave". Esto puede mejorar significativamente el rendimiento de las consultas cuando todas las columnas de la consulta se incluyen en el índice, ya sea como columnas con clave o sin clave, ya que no es necesario acceder a la propia tabla. Para obtener más información sobre las columnas incluidas de SQL Server, consulte la documentación.

En el ejemplo siguiente, la columna Url forma parte de la clave del índice, por lo que cualquier filtrado de consultas en esa columna puede usar el índice. Pero, además, las consultas que acceden solo a las columnas Title y PublishedOn no tendrán que acceder a la tabla y se ejecutarán de una forma más eficaz:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasIndex(p => p.Url)
        .IncludeProperties(
            p => new { p.Title, p.PublishedOn });
}

Restricciones CHECK

Las restricciones CHECK son una característica relacional estándar que permite definir una condición que debe contener todas las filas de una tabla; Cualquier intento de insertar o modificar datos que infrinja la restricción generará un error. Las restricciones CHECK son similares a las restricciones NON-NULL (que prohíben los valores NULL en una columna) o a las restricciones UNIQUE (que prohíben duplicados), pero permiten definir expresiones SQL arbitrarias.

Puede usar la API Fluent para especificar una restricción CHECK en una tabla, que se proporciona como una expresión SQL:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Product>()
        .ToTable(b => b.HasCheckConstraint("CK_Prices", "[Price] > [DiscountedPrice]"));
}

Se pueden definir varias restricciones CHECK en la misma tabla, cada una con su propio nombre.

Nota: Algunas restricciones CHECK comunes se pueden configurar a través del paquete de la comunidad EFCore.CheckConstraints.