我知道原帖是指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
子句之后附加其值。之后,根据传递给此方法的参数,我们可以最终终止语句或保留其原样。
为了使所有这些部分正常工作,我们必须通过覆盖
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保持不变。