Indici

Gli indici sono un concetto comune in molti archivi dati. Anche se l'implementazione nell'archivio dati può variare, vengono usate per rendere più efficienti le ricerche in base a una colonna (o a un set di colonne). Per altre informazioni sull'utilizzo di un indice valido, vedere la sezione relativa agli indici nella documentazione sulle prestazioni.

È possibile specificare un indice su una colonna come indicato di seguito:

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

Nota

Per convenzione, un indice viene creato in ogni proprietà (o set di proprietà) utilizzate come chiave esterna.

Indice composito

Un indice può anche estendersi su più di una colonna:

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

Indici su più colonne, noti anche come indici compositi, velocizzano le query che filtrano sulle colonne dell'indice, ma anche query che filtrano solo sulle prime colonne coperte dall'indice. Per altre informazioni, vedere la documentazione sulle prestazioni .

Univocità dell'indice

Per impostazione predefinita, gli indici non sono univoci: è consentito che più righe abbiano lo stesso valore per il set di colonne dell'indice. È possibile rendere univoco un indice come segue:

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

Se si tenta di inserire più di un'entità con gli stessi valori per il set di colonne dell'indice, verrà generata un'eccezione.

Ordinamento indice

Nota

Questa funzionalità viene introdotta in EF Core 7.0.

Nella maggior parte dei database ogni colonna coperta da un indice può essere crescente o decrescente. Per gli indici che coprono una sola colonna, questo in genere non è importante: il database può attraversare l'indice in ordine inverso in base alle esigenze. Tuttavia, per gli indici compositi, l'ordinamento può essere fondamentale per prestazioni ottimali e può indicare la differenza tra un indice usato da una query o meno. In generale, gli ordinamenti delle colonne di indice devono corrispondere a quelli specificati nella ORDER BY clausola della query.

L'ordinamento dell'indice è crescente per impostazione predefinita. È possibile impostare l'ordine decrescente di tutte le colonne come indicato di seguito:

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

È anche possibile specificare l'ordinamento in base a una colonna come indicato di seguito:

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

Denominazione degli indici e più indici

Per convenzione, gli indici creati in un database relazionale sono denominati IX_<type name>_<property name>. Per gli indici compositi, <property name> diventa un elenco di nomi di proprietà separati da caratteri di sottolineatura.

È possibile impostare il nome dell'indice creato nel database:

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

Si noti che se si chiama HasIndex più volte nello stesso set di proprietà, questo continua a configurare un singolo indice anziché crearne uno nuovo:

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

Poiché la seconda HasIndex chiamata esegue l'override della prima, viene creato solo un singolo indice decrescente. Può essere utile per configurare ulteriormente un indice creato per convenzione.

Per creare più indici sullo stesso set di proprietà, passare un nome a HasIndex, che verrà usato per identificare l'indice nel modello di Entity Framework e per distinguerlo da altri indici rispetto alle stesse proprietà:

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

Si noti che questo nome viene usato anche come valore predefinito per il nome del database, quindi la chiamata HasDatabaseName in modo esplicito non è necessaria.

Filtro dell'indice

Alcuni database relazionali consentono di specificare un indice filtrato o parziale. In questo modo è possibile indicizzare solo un subset di valori di una colonna, riducendo le dimensioni dell'indice e migliorando sia le prestazioni che l'utilizzo dello spazio su disco. Per altre informazioni sugli indici filtrati di SQL Server, vedere la documentazione.

È possibile usare l'API Fluent per specificare un filtro per un indice, fornito come espressione SQL:

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

Quando si usa il provider SQL Server EF aggiunge un 'IS NOT NULL' filtro per tutte le colonne nullable che fanno parte di un indice univoco. Per eseguire l'override di questa convenzione, è possibile specificare un null valore.

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

included_columns

Alcuni database relazionali consentono di configurare un set di colonne che vengono incluse nell'indice, ma non fanno parte della relativa "chiave". Ciò può migliorare significativamente le prestazioni delle query quando tutte le colonne della query vengono incluse nell'indice come colonne chiave o non chiave, perché non è necessario accedere alla tabella stessa. Per altre informazioni sulle colonne incluse in SQL Server, vedere la documentazione.

Nell'esempio seguente la Url colonna fa parte della chiave di indice, pertanto qualsiasi filtro di query su tale colonna può usare l'indice. Inoltre, le query che accedono solo alle Title colonne e PublishedOn non dovranno accedere alla tabella e verranno eseguite in modo più efficiente:

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

Vincoli CHECK

I vincoli Check sono una funzionalità relazionale standard che consente di definire una condizione che deve contenere per tutte le righe di una tabella; qualsiasi tentativo di inserimento o modifica dei dati che viola il vincolo avrà esito negativo. I vincoli Check sono simili ai vincoli non Null (che impediscono i valori Null in una colonna) o a vincoli univoci (che impediscono duplicati), ma consentono di definire un'espressione SQL arbitraria.

È possibile usare l'API Fluent per specificare un vincolo check in una tabella, fornito come espressione SQL:

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

È possibile definire più vincoli CHECK nella stessa tabella, ognuno con il proprio nome.

Nota: alcuni vincoli check comuni possono essere configurati tramite il pacchetto della community EFCore.CheckConstraints.