Índices

Os índices são um conceito comum entre vários armazenamentos de dados. Embora sua implementação no armazenamento de dados possa variar, eles são usados para tornar mais eficientes as pesquisas baseadas em uma coluna (ou conjunto de colunas). Consulte a seção índices na documentação de desempenho para obter mais informações sobre o bom uso do índice.

Especifique um índice em uma coluna da seguinte maneira:

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

Observação

Por convenção, um índice é criado em cada propriedade (ou conjunto de propriedades) que são usados como uma chave estrangeira.

Índice composto

Um índice também pode abranger mais de uma coluna:

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

Os índices em várias colunas, também conhecidos como índices compostos, aceleram as consultas que filtram as colunas do índice, mas também as consultas que filtram apenas as primeiras colunas cobertas pelo índice. Consulte a documentação de desempenho para obter mais informações.

Exclusividade do índice

Por padrão, os índices não são exclusivos: várias linhas têm permissão para ter os mesmos valores para o conjunto de colunas do índice. Faça um índice exclusivo da seguinte maneira:

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

A tentativa de inserir mais de uma entidade com os mesmos valores para o conjunto de colunas do índice fará com que uma exceção seja gerada.

Ordem de classificação de índices

Observação

Essa funcionalidade está sendo introduzida no EF Core 7.0.

Na maioria dos bancos de dados, cada coluna coberta por um índice pode ser crescente ou decrescente. Para índices que abrangem apenas uma coluna, isso normalmente não importa: o banco de dados pode percorrer o índice em ordem inversa conforme necessário. No entanto, para índices compostos, a ordenação pode ser determinante para um bom desempenho e pode significar a diferença entre um índice sendo usado por uma consulta ou não. Em geral, as ordens de classificação das colunas de índice devem corresponder às especificadas na cláusula ORDER BY da consulta.

A ordem de classificação do índice é crescente por padrão. Faça com que todas as colunas tenham ordem decrescente da seguinte maneira:

[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; }
}

Poderá também especificar a ordem de classificação coluna a coluna, da seguinte maneira:

[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 índice e vários índices

Por convenção, os índices criados em um banco de dados relacional são nomeados IX_<type name>_<property name>. Para índices compostos, <property name> torna-se uma lista de nomes de propriedade separados por sublinhado.

Defina o nome do índice criado no banco de dados:

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

Observe que, se você chamar HasIndex mais de uma vez no mesmo conjunto de propriedades, isso continuará a configurar apenas um índice, em vez de criar um:

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();

Como a segunda chamada HasIndex substitui a primeira, isso cria apenas um índice descendente. Isso será útil caso você queira configurar mais detalhadamente um índice que foi criado por convenção.

Para criar vários índices sobre o mesmo conjunto de propriedades, passe um nome para o HasIndex, que será usado para identificar o índice no modelo EF e distingui-lo de outros índices nas mesmas propriedades:

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();

Observe que esse nome também é usado como padrão para o nome do banco de dados, portanto, não é necessário chamar HasDatabaseName explicitamente.

Filtro de índice

Alguns bancos de dados relacionais permitem especificar um índice filtrado ou parcial. Isso permite indexar apenas um subconjunto dos valores de uma coluna, reduzindo o tamanho do índice e melhorando o desempenho e o uso do espaço em disco. Para obter mais informações sobre os índices filtrados do SQL Server consulte a documentação.

Use a API Fluent para especificar um filtro em um índice, fornecido como uma expressão de SQL:

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

Ao usar o provedor de SQL Server o EF adiciona um filtro 'IS NOT NULL' para todas as colunas anuláveis que fazem parte de um índice exclusivo. Para substituir essa convenção, forneça um valor null.

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

Colunas incluídas

Alguns bancos de dados relacionais permitem configurar um conjunto de colunas que são incluídas no índice, mas que não fazem parte de sua "chave". Isso pode melhorar significativamente o desempenho da consulta, quando todas as colunas na consulta são incluídas no índice como colunas de chave ou não chave, pois a tabela em si não precisa ser acessada. Para obter mais informações sobre as colunas incluídas do SQL Server consulte a documentação.

No exemplo a seguir, a coluna Url faz parte da chave de índice, portanto, qualquer filtragem de consulta nessa coluna pode usar o índice. Mas, além disso, as consultas que acessam apenas as colunas Title e PublishedOn, não precisarão acessar a tabela e serão executadas com mais eficiência:

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

Verificar restrições

As restrições de verificação são um recurso relacional padrão que permite definir uma condição que deve ser válido para todas as linhas em uma tabela. Qualquer tentativa de inserir ou modificar dados que violem a restrição falhará. As restrições de verificação são semelhantes a restrições não nulas (que proíbem nulos em uma coluna) ou as restrições exclusivas (que proíbem duplicatas), mas permitem que a expressão de linguagem SQL arbitrária seja definida.

Use a API Fluente para especificar uma restrição de verificação em uma tabela, fornecida como uma expressão de linguagem SQL:

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

Várias restrições de verificação podem ser definidas na mesma tabela, cada uma com seu próprio nome.

Observação: algumas restrições de verificação comuns podem ser configuradas por meio do pacote da comunidade EFCore.CheckConstraints.