インデックス

インデックスは、多くのデータ ストアに共通する概念です。 データ ストアによって実装はさまざまですが、インデックスは列 (または列のセット) に基づいてより効率的に検索を行うために使用されます。 インデックスを適切に使用する方法の詳細については、パフォーマンスに関するドキュメントのインデックスのセクションを参照してください。

列に対してインデックスを指定するには、次のようにします。

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

Note

規則により、インデックスは外部キーとして使用される各プロパティ (またはプロパティのセット) に作成されます。

複合インデックス

複数の列にまたがってインデックスを作成することができます。

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

複数の列に対するインデックス ("複合インデックス" とも呼ばれます) を使用すると、インデックスの列をフィルター処理するクエリが高速化されますが、インデックスの対象となる "最初の" 列に対してのみフィルター処理するクエリも高速化されます。 詳細については、パフォーマンスに関するドキュメントを参照してください。

インデックスの一意性

既定では、インデックスは一意ではありません。インデックスの列セットでは複数の行に同じ値を指定できます。 次のようにして、インデックスを一意にすることができます。

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

インデックスの列セットに対して同じ値の複数のエンティティを挿入しようとすると、例外がスローされます。

インデックスの並べ替え順序

Note

この機能は EF Core 7.0 で導入されています。

ほとんどのデータベースでは、インデックスの対象となる各列を昇順または降順にすることができます。 これは通常、1 つの列のみを対象とするインデックスの場合は問題ありません。データベースは必要に応じて逆の順序でインデックスを走査できます。 ただし、複合インデックスの場合、順序付けはパフォーマンス向上のためにきわめて重要になることがあり、インデックスがクエリで使用されるかされないかの分かれ目になる可能性があります。 一般に、インデックス列の並べ替え順序は、クエリの ORDER BY 句で指定されているものと一致する必要があります。

既定では、インデックスの並べ替え順序は昇順です。 次のように、すべての列を降順にすることができます。

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

次のように、列ごとに並べ替え順序を指定することもできます。

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

インデックスの名前付けと複数のインデックス

規則により、リレーショナル データベースで作成されたインデックスの名前は IX_<type name>_<property name> となります。 複合インデックスの場合、<property name> は、アンダースコアで区切られたプロパティ名のリストになります。

データベースで作成されたインデックスの名前を設定できます。

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

同じプロパティ セットに対して HasIndex を複数回呼び出すと、新しく作成されず、1 つのインデックスが引き続き構成されることに注意してください。

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

2 回目の HasIndex の呼び出しは 1 回目をオーバーライドするため、これで 1 つの降順インデックスのみが作成されます。 これは、規約に従って作成されたインデックスをさらに構成する場合に役立ちます。

同じプロパティ セットに対して複数のインデックスを作成するには、名前を HasIndex に渡します。これは、EF モデル内のインデックスを特定し、同じプロパティに対する他のインデックスと区別するために使われます。

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

この名前はデータベース名の既定値としても使われるため、明示的に HasDatabaseName を呼び出す必要はないことに注意してください。

インデックス フィルター

一部のリレーショナル データベースでは、フィルター選択されたインデックスまたは部分的なインデックスを指定できます。 これにより、列の値のサブセットのみにインデックスを作成することで、インデックスのサイズを減らし、パフォーマンスとディスク領域使用率の両方を向上させることができます。 SQL Server のフィルター選択されたインデックスの詳細については、こちらのドキュメントを参照してください

SQL 式として指定することで、Fluent API を使用してインデックスにフィルターを指定できます。

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

SQL Server プロバイダーを使用すると、EF で、一意のインデックスの一部であるすべての Null 許容列に対して 'IS NOT NULL' フィルターを追加できます。 この規則をオーバーライドするには、null 値を指定します。

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

[付加列]

一部のリレーショナル データベースでは、インデックスに含まれるが、"キー" には含まれない列のセットを構成できます。 これにより、クエリ内のすべての列がキー列または非キー列としてインデックスに含まれている場合、テーブル自体にアクセスする必要がなくなるため、クエリのパフォーマンスが大幅に向上する可能性があります。 SQL Server の付加列の詳細については、こちらのドキュメントを参照してください

次の例では、Url 列はインデックス キーの一部なので、その列に対してフィルター処理を行うクエリではインデックスを使用できます。 ただし、さらに、Title 列と PublishedOn 列にのみアクセスするクエリはテーブルにアクセスする必要がないため、より効率的に実行されます。

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

CHECK 制約

CHECK 制約は、テーブル内のすべての行に対して保持する必要がある条件を定義できる標準的なリレーショナル機能です。制約に違反するデータを挿入または変更しようとすると、すべて失敗します。 CHECK 制約は、非 null 値の制約 (列で null 値を禁止する制約) や一意の制約 (重複を禁止する制約) に似ていますが、任意の SQL 式を定義できます。

SQL 式として指定することで、Fluent API を使用してテーブルに CHECK 制約を指定できます。

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

同じテーブルに対して複数の CHECK 制約を定義でき、それぞれに独自の名前を付けることができます。

注: 一部の一般的な CHECK 制約は、コミュニティ パッケージ EFCore.CheckConstraints を使用して構成できます。