Entity Framework 过滤器索引

Entity Framework Filter Index

我使用 EF 6。1.x Code First。

我了解到最新的 EF 不支持带有过滤器表达式的索引。

SO也没有解决方案:

EF 6.1 Unique Nullable Index

一年后,使过滤器索引与 Code First 和 DbMigrations 一起工作的工作方式是什么?

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

在 EF 6.1 中,使用 Code First 和 DbMigrations 的工作方法是使用 DbMigration class:

中的 Sql 方法
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");
    }
}

但我知道你可能在问你是否可以 create an index using the IndexAttribute introduced in 6.1,但是使用过滤器 - 答案是 "No"

几乎重复:Entity Framework 6.1 - Create index with INCLUDE statement

我知道原来的post指的是EF的6.1版本,但经过一番研究,我找到了一种方法,可以将过滤索引的扩展方法添加到[的流畅api中=54=]EF Core(1.1 版本)。也许有人会发现这很有用(也许在旧版本中也有实现它的方法)。 不过我得警告你。由于此解决方案使用 Microsoft.EntityFrameworkCore.Migrations.InternalMicrosoft.EntityFrameworkCore.Infrastructure 命名空间中的 classes,因此无法保证此代码在 EF 更新后仍然有效。这些命名空间中每个 class 的摘要中都包含一条消息,说

This API may change or be removed in future releases

,所以你被警告了。

但是切中要点。

首先你必须为 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)。如果我们的注释被设置,我们可以在 SQL 语句末尾的 WHERE 子句之后附加它的值。之后,根据传递给此方法的参数,我们最终可以终止语句或保持原样。

为了使所有这些部分正常工作,我们必须通过覆盖 DbContext.

OnConfiguring 方法,用新版本替换旧服务。
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 命令后,我们将看到结果 SQL 如下:

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

此方法实际上可用于将任何自定义 SQL 生成器包含到 ef core fluent api 中。至少只要 EF API 保持不变。

请注意,现在 EF core 2.1.X 通过 IndexBuilder 上的 HasFilter 扩展添加了对过滤索引的内置支持,因此不再需要自定义实现.

有关详细信息,请参阅 this