【问题标题】: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))

【问题讨论】:

标签: entity-framework entity-framework-6 ef-code-first


【解决方案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 保持不变。

    【讨论】:

      猜你喜欢
      • 2014-03-31
      • 2017-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多