Поделиться через


Индексы

Индексы широко используются во многих хранилищах данных. Хотя их реализации в хранилище данных может различаться, они используются для более эффективного поиска по столбцу (или набору столбцов). Дополнительные сведения о правильном использовании индексов см. в разделе об индексах в документации по производительности.

Индекс для столбца можно указать следующим образом:

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

Примечание.

По соглашению индекс создается в каждом свойстве (или наборе свойств), используемом в качестве внешнего ключа.

Составной индекс

Индекс может также охватывать более одного столбца:

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

При попытке вставить более одной сущности с одинаковыми значениями для набора столбцов индекса возникнет исключение.

Порядок сортировки индексов

Примечание.

Эта возможность появилась в EF Core 7.0.

В большинстве баз данных каждый столбец, охватываемый индексом, можно упорядочить либо по возрастанию, либо по убыванию. Для индексов, охватывающих только один столбец, обычно это не имеет значения: база данных может пройти по индексу в обратном порядке по мере необходимости. Но при использовании составных индексов порядок может иметь решающее значение для производительности и определять, используется ли индекс запросом. Как правило, порядок сортировки столбцов индекса должен соответствовать указанному в предложении 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 нескольких раз в одном наборе свойств, который продолжает настраивать один индекс, а не создавать новый:

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

Так как второй HasIndex вызов переопределяет первый, он создает только один, убывая индекс. Это может быть полезно для дальнейшей настройки индекса, созданного соглашением.

Чтобы создать несколько индексов по одному набору свойств, передайте имя 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 см. в документации.

Вы можете использовать Fluent API для указания фильтра по индексу, который предоставляется в виде SQL-выражения:

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

При использовании поставщика SQL Server EF добавляет фильтр 'IS NOT NULL' для всех столбцов, допускающих значение 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 });
}

Проверочные ограничения

Проверочные ограничения — это стандартная реляционная функция, позволяющая определить условие, которое должно выполняться для всех строк в таблице. Любая попытка вставить или изменить данные, нарушающие ограничение, завершится ошибкой. Проверочные ограничения похожи на ограничения, запрещающие значение NULL в столбце, или на ограничения, которые запрещают дублирование, но они позволяют определять произвольное SQL-выражение.

Вы можете использовать Fluent API, чтобы указать проверочное ограничение для таблицы в виде SQL-выражения:

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

В одной таблице можно определить несколько проверочных ограничений, каждое с отдельным именем.

Примечание. Некоторые распространенные проверочные ограничения можно настроить с помощью пакета от сообщества EFCore.CheckConstraints.