实体框架过滤器索引

Posted

技术标签:

【中文标题】实体框架过滤器索引【英文标题】:Entity Framework Filter Index 【发布时间】:2015-12-29 22:03:18 【问题描述】:

我使用 EF 6.1.x 代码优先。

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

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

【问题讨论】:

趣味文章:***.com/questions/29922099/… Official EntityFramework documentation for Index filters. 【参考方案1】:

在 EF 6.1 中,使用 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");
    

但我知道您可能会问是否可以 create an index using the IndexAttribute introduced in 6.1,但使用过滤器 - 答案是“否”

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

【讨论】:

是的,我正在寻找 blog.oneunicorn.com/2014/02/15/… 但我知道实际上它还不可能。还是谢谢! 我已经添加了这个部分类并做了“update-database”然后所有迁移都被明确应用但是这个新索引没有在数据库中创建??? 好吧,我先创建了一个空的迁移,然后我得到这个错误:错误号:102,状态:1,类:15 ')' 附近的语法不正确。请您更正“)”错误吗? 糟糕,我在您的 sql 中复制了没有删除导致语法错误的杂散逗号。编辑修复它。【参考方案2】:

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

详情请见this

【讨论】:

【参考方案3】:

我知道原帖指的是6.1版本的EF,但经过一番研究,我找到了一种方法,将过滤索引的扩展方法添加到EF Core(1.1版本)的fluent api中强>。也许有人会发现这很有用(也许在旧版本中也有一种方法可以实现)。 不过我必须警告你。由于此解决方案使用 Microsoft.EntityFrameworkCore.Migrations.InternalMicrosoft.EntityFrameworkCore.Infrastructure 命名空间中的类,因此无法保证此代码在 EF 更新后可以正常工作。这些命名空间中每个类的摘要中都包含一条消息,说明

此 API 可能会在未来的版本中更改或删除

,所以你已经被警告了。

但切中要害。

首先您必须为IndexBuilder 创建一个标准扩展方法。它的主要职责是为构建的索引添加一个带有条件的新注释。之后将使用 fluent 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 commad 后,我们将看到如下生成的 SQL:

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

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

【讨论】:

以上是关于实体框架过滤器索引的主要内容,如果未能解决你的问题,请参考以下文章

在实体框架中使用 bindingSource 的过滤器

对实体进行排序和过滤 ListProperty 而不会导致索引爆炸

实体框架4:使用自我跟踪实体的过滤器进行预先加载(包括)

您如何在实体框架的数据库级别执行复杂的“或”过滤器?

实体框架过滤器“表达式<Func<T, bool>>”

使用实体框架过滤BindingSource