Index

Les index sont un concept commun dans de nombreux magasins de données. Même si leur implémentation dans le magasin de données peut varier, ils sont utilisés pour rendre les recherches plus efficaces en fonction d’une colonne (ou d’un ensemble de colonnes). Consultez la section Index de la documentation sur les performances pour plus d’informations sur l’utilisation correcte des index.

Vous pouvez spécifier un index sur une colonne comme suit :

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

Remarque

Par convention, un index est créé dans chaque propriété (ou ensemble de propriétés) utilisée comme clé étrangère.

Index composite

Un index peut également s’étendre sur plusieurs colonnes :

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

Les index sur plusieurs colonnes, également appelés index composites, accélèrent les requêtes qui filtrent sur les colonnes de l’index, mais aussi les requêtes qui filtrent uniquement sur les premières colonnes couvertes par l’index. Pour plus d’informations, consultez la documentation sur les performances.

Unicité des index

Par défaut, les index ne sont pas uniques : plusieurs lignes sont autorisées à avoir la ou les mêmes valeurs pour l’ensemble de colonnes de l’index. Vous pouvez rendre un index unique comme suit :

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

La tentative d’insertion de plusieurs entités avec les mêmes valeurs pour l’ensemble de colonnes de l’index entraîne la levée d’une exception.

Ordre de tri des index

Remarque

Cette fonctionnalité est introduite dans EF Core 7.0.

Dans la plupart des bases de données, chaque colonne couverte par un index peut être dans l’ordre croissant ou décroissant. Pour les index couvrant une seule colonne, cela n’a généralement pas d’importance : la base de données peut parcourir l’index dans l’ordre inverse si nécessaire. Toutefois, pour les index composites, le tri peut être crucial pour de bonnes performances et peut signifier la différence entre un index utilisé par une requête ou non. En général, les ordres de tri des colonnes d’index doivent correspondre à ceux spécifiés dans la clause ORDER BY de votre requête.

L’ordre de tri des index est croissant par défaut. Vous pouvez faire en sorte que toutes les colonnes soient dans l’ordre décroissant comme suit :

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

Vous pouvez également spécifier l’ordre de tri colonne par colonne comme suit :

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

Nommage d’index et index multiples

Par convention, les index créés dans une base de données relationnelle sont nommés IX_<type name>_<property name>. Pour les index composites, <property name> devient une liste séparée par un trait de soulignement des noms de propriétés.

Vous pouvez définir le nom de l’index créé dans la base de données :

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

Notez que si vous appelez HasIndex plus d’une fois sur le même ensemble de propriétés, cette opération poursuit la configuration d’un seul index plutôt que la création d’un nouvel index :

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

Étant donné que le deuxième HasIndex appel remplace le premier, cela ne crée qu’un seul index décroissant. Cette méthode peut être utile pour configurer un index créé par convention.

Pour créer des index multiples sur le même ensemble de propriétés, transmettez un nom au HasIndex, qui sera utilisé pour identifier l’index dans le modèle EF et pour le distinguer d’autres index parmi les mêmes propriétés :

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

Notez que ce nom est également utilisé par défaut pour le nom de la base de données. Par conséquent, il n’est pas nécessaire d’appeler HasDatabaseName explicitement.

Filtre d’index

Certaines bases de données relationnelles vous permettent de spécifier un index filtré ou partiel. Cela vous permet d’indexer uniquement un sous-ensemble des valeurs d’une colonne, ce qui réduit la taille de l’index et améliore à la fois les performances et l’utilisation de l’espace disque. Pour plus d’informations sur les index filtrés SQL Server, consultez la documentation.

Vous pouvez utiliser l’API Fluent pour spécifier un filtre sur un index, fourni en tant qu’expression SQL :

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

Lorsque vous utilisez le fournisseur SQL Server, EF ajoute un filtre 'IS NOT NULL' pour toutes les colonnes pouvant accepter la valeur Null qui font partie d’un index unique. Pour remplacer cette convention, vous pouvez fournir une valeur null.

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

Colonnes incluses

Certaines bases de données relationnelles vous permettent de configurer un ensemble de colonnes qui sont incluses dans l’index, mais ne font pas partie de sa « clé ». Cela peut améliorer considérablement les performances des requêtes lorsque toutes les colonnes de la requête sont incluses dans l’index en tant que colonnes clés ou non clés, car la table elle-même n’a pas besoin d’être accessible. Pour plus d’informations sur les colonnes incluses de SQL Server, consultez la documentation.

Dans l’exemple suivant, la colonne Url fait partie de la clé d’index ; ainsi, tout filtrage de requête sur cette colonne peut utiliser l’index. En outre, les requêtes qui accèdent uniquement aux colonnes Title et PublishedOn n’ont pas besoin d’accéder à la table et s’exécutent plus efficacement :

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

Contraintes de validation

Les contraintes de vérification constituent une fonctionnalité relationnelle standard qui vous permet de définir une condition qui doit contenir toutes les lignes d’une table ; toute tentative d’insertion ou de modification de données qui enfreint la contrainte échoue. Les contraintes de vérification sont similaires aux contraintes non null (qui interdisent les valeurs Null dans une colonne) ou aux contraintes uniques (qui interdisent les doublons), mais autorisent la définition d’une expression SQL arbitraire.

Vous pouvez utiliser l’API Fluent pour spécifier une contrainte de vérification sur une table, fournie en tant qu’expression SQL :

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

Plusieurs contraintes de vérification peuvent être définies sur la même table, chacune portant son propre nom.

Remarque : certaines contraintes de vérification courantes peuvent être configurées via le package de communauté EFCore.CheckConstraints.