实体框架过滤器索引

27

我使用的是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))

有趣文章:https://dev59.com/Forda4cB1Zd3GeqPJz4b - Elisabeth
3个回答

34
在 EF 6.1 中,使“this”与 Code First 和 DbMigrations 协同工作的工作方式是使用 DbMigration 类中的 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");
    }
}

但我意识到你可能在问是否可以使用6.1中引入的IndexAttribute创建带有过滤器的索引 - 答案是“不行”

几乎与此类似:Entity Framework 6.1 - 使用INCLUDE语句创建索引


是的,我正在寻找http://blog.oneunicorn.com/2014/02/15/ef-6-1-creating-indexes-with-indexattribute/,但我知道目前还不可能。还是谢谢! - Elisabeth
我已经添加了这个部分类,并执行了 "update-database",然后所有的迁移都被明确地应用了,但是这个新索引没有在数据库中创建? - Elisabeth
好的,我首先创建了一个空迁移,但是接着我遇到了这个错误:错误编号:102,状态:1,类别:15。附近有“)”的语法不正确。请您纠正这个“)”错误? - Elisabeth
抱歉,我复制了您的 SQL 代码,但没有删除多余的逗号,导致语法错误。已经进行了编辑以修复此问题。 - Colin

10
请注意,目前EF Core 2.1.x通过IndexBuilder上的HasFilter扩展功能内置支持过滤索引,因此不再需要自定义实现。
更多详情请参见此处

4
我知道原帖是指EF 6.1版本,但在一些研究后,我找到了一种方法,可以为EF Core(1.1版本)的流畅 API 添加过滤索引的扩展方法。也许有人会发现这很有用(也许还有一种实现方式可以应用于旧版本)。
然而,我必须警告你。由于这个解决方案使用了来自 Microsoft.EntityFrameworkCore.Migrations.Internal 和 Microsoft.EntityFrameworkCore.Infrastructure 命名空间内的类,因此不能保证此代码在 EF 更新后仍能正常工作。每个类的摘要中都包含一个消息,它说:
“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子句之后附加其值。之后,根据传递给此方法的参数,我们可以最终终止语句或保留其原样。
为了使所有这些部分正常工作,我们必须通过覆盖DbContextOnConfiguring方法来用新版本替换旧服务。
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保持不变。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接