Indizes

Indizes sind ein gängiges Konzept für zahlreiche Datenspeicher. Ihre Implementierung im Datenspeicher kann variieren. Grundsätzlich werden sie jedoch verwendet, um Suchfunktionen basierend auf einer Spalte (oder einer Gruppe von Spalten) effizienter zu gestalten. Weitere Informationen zur sinnvollen Indexverwendung finden Sie im Abschnitt zu Indizes in der Dokumentation zur Leistung.

Sie können einen Index für eine Spalte wie folgt angeben:

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

Hinweis

Gemäß der Konvention wird in jeder Eigenschaft (oder einer Gruppe von Eigenschaften), die als Fremdschlüssel verwendet wird, ein Index erstellt.

Zusammengesetzter Index

Ein Index kann sich auch über mehrere Spalten erstrecken:

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

Indizes über mehrere Spalten werden auch als zusammengesetzte Indizes bezeichnet. Sie beschleunigen Abfragen, die nach Indexspalten filtern, aber auch Abfragen, die nur nach den ersten vom Index abgedeckten Spalten filtern. Weitere Informationen finden Sie in der Dokumentation zur Leistung.

Eindeutigkeit des Indexes

Indizes sind standardmäßig nicht eindeutig: Mehrere Zeilen dürfen dieselben Werte für die Spaltengruppe des Indexes aufweisen. Sie können einen Index wie folgt als eindeutig festlegen:

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

Der Versuch, mehrere Entitäten mit denselben Werten für die Spaltengruppe des Indexes einzufügen, führt zu einer Ausnahme.

Sortierreihenfolge für Indizes

Hinweis

Diese Funktion wird in EF Core 7.0 eingeführt.

In den meisten Datenbanken kann jede Spalte, die durch einen Index abgedeckt ist, entweder aufsteigend oder absteigend sortiert sein. Für Indizes, die nur eine Spalte abdecken, ist dies in der Regel nicht wichtig: Die Datenbank kann den Index in umgekehrter Reihenfolge durchlaufen, falls erforderlich. Für zusammengesetzte Indizes kann die Reihenfolge jedoch für eine gute Leistung entscheidend sein und den Unterschied zwischen zwei Indizes bedeuten, von denen der eine von einer Abfrage verwendet wird und der andere nicht. Im Allgemeinen sollten die Sortierreihenfolgen der Indexspalten den in der Klausel ORDER BY Ihrer Abfrage angegebenen entsprechen.

Die Indexsortierreihenfolge ist standardmäßig aufsteigend. Sie können für alle Spalten wie folgt eine absteigende Reihenfolge festlegen:

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

Sie können die Sortierreihenfolge auch spaltenweise wie folgt festlegen:

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

Indexbenennung und Mehrfachindizes

Gemäß der Konvention werden Indizes, die in einer relationalen Datenbank erstellt werden, mit IX_<type name>_<property name> benannt. Bei zusammengesetzten Indizes wird <property name> zu einer durch Unterstriche getrennten Liste von Eigenschaftennamen.

Sie können den Namen des Indexes festlegen, der in der Datenbank erstellt wird:

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

Beachten Sie, dass bei HasIndex mehreren Aufrufen für dieselbe Gruppe von Eigenschaften weiterhin ein einzelner Index konfiguriert wird, anstatt einen neuen zu erstellen:

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

Da der zweite HasIndex-Aufruf den ersten überschreibt, wird dadurch nur ein einzelner absteigender Index erstellt. Dies kann hilfreich sein, um einen Index weiter zu konfigurieren, der von der Konvention erstellt wurde.

Um mehrere Indizes über dieselbe Gruppe von Eigenschaften zu erstellen, übergeben Sie einen Namen an den HasIndex, der verwendet wird, um den Index im EF-Modell zu identifizieren und ihn von anderen Indizes über dieselben Eigenschaften zu unterscheiden:

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

Beachten Sie, dass dieser Name auch als Standard für den Datenbanknamen verwendet wird, sodass explizite Aufrufe HasDatabaseName nicht erforderlich sind.

Indexfilter

Bei einigen relationalen Datenbanken können Sie einen gefilterten oder partiellen Index angeben. Dadurch können Sie nur eine Teilmenge der Werte einer Spalte indizieren, wodurch sich die Indexgröße reduziert und sowohl die Leistung als auch die Speicherplatznutzung verbessern. Weitere Informationen zu gefilterten Indizes in SQL Server finden Sie in der Dokumentation.

Mithilfe der Fluent-API können Sie einen Filter für einen Index angeben, der als SQL-Ausdruck bereitgestellt wird:

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

Bei Verwendung des SQL Server-Anbieters fügt EF einen Filter 'IS NOT NULL' für alle Spalten hinzu, die NULL-Werte zulassen und einem eindeutigen Index angehören. Um diese Konvention zu überschreiben, können Sie einen null-Wert angeben.

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

Eingeschlossene Spalten

Einige relationale Datenbanken ermöglichen Ihnen die Konfiguration einer Gruppe von Spalten, die zwar in den Index aufgenommen werden, aber nicht Teil des zugehörigen „Schlüssels“ sind. Dadurch lässt sich die Abfrageleistung erheblich verbessern, wenn alle Spalten in der Abfrage entweder als Schlüsselspalten oder als Nichtschlüsselspalten im Index enthalten sind, weil auf die Tabelle selbst nicht zugegriffen werden muss. Weitere Informationen zu in SQL Server enthaltenen Indizes finden Sie in der Dokumentation.

Im folgenden Beispiel gehört die Url-Spalte zum Indexschlüssel, sodass der Index bei jeder Abfragefilterung für diese Spalte verwendet werden kann. Darüber hinaus müssen Abfragen, die nur auf die Spalten Title und PublishedOn zugreifen, nicht auf die Tabelle zugreifen und werden somit effizienter ausgeführt:

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

Check-Einschränkungen

Check-Einschränkungen sind ein relationales Standardfeature, mit dem Sie eine Bedingung definieren können, die durch alle Zeilen in einer Tabelle erfüllt werden muss. Jeder Versuch zum Einfügen oder Ändern von Daten, der gegen die Einschränkung verstößt, führt zum Fehler. Check-Einschränkungen ähneln Nicht-NULL-Einschränkungen (die NULL-Werte in einer Spalte verbieten) oder Unique-Einschränkungen (die Duplikate verhindern), lassen jedoch die Definition beliebiger SQL-Ausdrücke zu.

Mithilfe der Fluent-API können Sie eine Check-Einschränkung für eine Tabelle angeben, die als SQL-Ausdruck bereitgestellt wird:

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

Für dieselbe Tabelle können mehrere Check-Einschränkungen mit jeweils eigenen Namen definiert werden.

Hinweis: Einige allgemeine Check-Einschränkungen können über das Communitypaket EFCore.CheckConstraints konfiguriert werden.