Подтвердить что ты не робот

Индекс фильтра платформы Entity

Я использую сначала код EF 6.1.x.

Я прочитал, что индекс с выражением фильтра не поддерживается EF последним.

На SO также нет решения:

EF 6.1 Уникальный индекс Nullable

Через год, какой рабочий способ сделать индекс фильтра работать с Code First и DbMigrations?

CREATE UNIQUE NONCLUSTERED INDEX [IX_DefaultLanguageApplicationId] ON [dbo].[Languages]
(
    [IsDefaultLanguage] ASC,
    [ApplicationId] ASC,
)
WHERE ([IsDefaultLanguage]=(1))
4b9b3361

Ответ 1

В EF 6.1 рабочий способ сделать эту работу с Code First и DbMigrations заключается в использовании метода Sql в классе DbMigration:

public partial class AddIndexes : DbMigration
{
    public override void Up()
    {
        Sql(@"CREATE UNIQUE NONCLUSTERED INDEX
             [IX_DefaultLanguageApplicationId] ON [dbo].[Languages]
             (
                [IsDefaultLanguage] ASC,
                [ApplicationId] ASC 
             )
             WHERE ([IsDefaultLanguage]=(1))");

    }

    public override void Down()
    {
        DropIndex("dbo.Languages", "IX_DefaultLanguageApplicationId");
    }
}

Но я понимаю, что вы, вероятно, спрашиваете, можете ли вы создать индекс, используя IndexAttribute, представленный в 6.1, но с фильтром - ответ это "Нет"

Почти дубликат: Entity Framework 6.1 - Создайте индекс с инструкцией INCLUDE

Ответ 2

Я знаю, что исходное сообщение относится к версии EF версии 6.1, но после некоторого исследования я нашел способ добавить метод расширения для отфильтрованных индексов к текущему api EF Core (версия 1.1). Возможно, кто-то найдет это полезным (и, возможно, есть способ реализовать это и в более старых версиях). Я должен вас предупредить. Поскольку в этом решении используются классы из пространств имен Microsoft.EntityFrameworkCore.Migrations.Internal и Microsoft.EntityFrameworkCore.Infrastructure, он не гарантирует, что этот код будет работать после обновления EF. Существует массажа, включенного в резюме каждого класса в этих пространствах имен, в котором говорится, что

Этот API может быть изменен или удален в будущих выпусках

поэтому вы были предупреждены.

Но к точке.

Сначала вам нужно создать стандартный метод расширения для IndexBuilder. Его основная ответственность будет заключаться в добавлении новой аннотации с условием к построенному индексу. После этого этот метод будет использоваться с беглым api. Чтобы не называть нашу аннотацию SqlServer:FilteredIndex.

static class FilteredIndexExtension
{
    public static IndexBuilder Filtered(this IndexBuilder indexBuilder, string condition)
    {
        indexBuilder.HasAnnotation("SqlServer:FilteredIndex", condition);

        return indexBuilder;
    }
}

Затем вы должны разрешить фактическое включение этой аннотации внутри миграций. Вы должны переопределить поведение по умолчанию SqlServerMigrationsAnnotationProvider для разработчиков индексов.

class ExtendedSqlServerMigrationsAnnotationProvider : SqlServerMigrationsAnnotationProvider
{
    public override IEnumerable<IAnnotation> For(IIndex index)
    {
        var baseAnnotations = base.For(index);
        var customAnnotatinos = index.GetAnnotations().Where(a => a.Name == "SqlServer:FilteredIndex");

        return baseAnnotations.Concat(customAnnotatinos);
    }
}

Теперь самая сложная часть. Мы должны переопределить поведение по умолчанию SqlServerMigrationsSqlGenerator по отношению к индексам.

class ExtendedSqlServerMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
{
    public ExtendedSqlServerMigrationsSqlGenerator(IRelationalCommandBuilderFactory commandBuilderFactory, ISqlGenerationHelper sqlGenerationHelper, IRelationalTypeMapper typeMapper, IRelationalAnnotationProvider annotations, IMigrationsAnnotationProvider migrationsAnnotations) : base(commandBuilderFactory, sqlGenerationHelper, typeMapper, annotations, migrationsAnnotations)
    {
    }

    protected override void Generate(CreateIndexOperation operation, IModel model, MigrationCommandListBuilder builder, bool terminate)
    {
        base.Generate(operation, model, builder, false);

        var filteredIndexCondition = operation.FindAnnotation("SqlServer:FilteredIndex");

        if (filteredIndexCondition != null)
            builder.Append($" WHERE {filteredIndexCondition.Value}");

        if (terminate)
        {
            builder.AppendLine(SqlGenerationHelper.StatementTerminator);
            EndStatement(builder);
        }
    }
}

Как вы можете видеть, мы вызываем базовый генератор здесь, поэтому наше условие будет добавлено в конце его, не изменяя его. Мы должны помнить, что здесь не следует останавливать базовый оператор SQL (последний аргумент, переданный методу base.Generate, равен false). Если наша аннотация установлена, мы можем добавить ее значение после предложения WHERE в конце инструкции SQL. После этого, в зависимости от аргумента, переданного этому методу, мы можем, наконец, завершить утверждение или оставить его как есть.

Для всех этих частей мы должны заменить старые службы новыми версиями, переопределив метод OnConfiguring нашего DbContext.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.ReplaceService<SqlServerMigrationsAnnotationProvider, ExtendedSqlServerMigrationsAnnotationProvider>();
        optionsBuilder.ReplaceService<SqlServerMigrationsSqlGenerator, ExtendedSqlServerMigrationsSqlGenerator>();
    }

Теперь мы можем использовать наш метод расширения следующим образом:

builder.HasIndex(a => a.Identity).IsUnique().Filtered("[End] IS NULL");

Он будет генерировать миграцию следующим образом:

migrationBuilder.CreateIndex(
            name: "IX_Activities_Identity",
            table: "Activities",
            column: "Identity",
            unique: true)
            .Annotation("SqlServer:FilteredIndex", "[End] IS NULL");

И после вызова Script-Migration commad в консоли диспетчера пакетов мы увидим результирующий SQL как это:

CREATE UNIQUE INDEX [IX_Activities_Identity] ON [Activities] ([Identity]) WHERE [End] IS NULL;

Этот метод может быть фактически использован для включения любого настраиваемого генератора SQL в свободный поток api. По крайней мере, пока API EF остается тем же.