EF6 防止不在外键上创建索引

EF6 preventing not to create Index on Foreign Key

我正在使用 EF6 代码优先方法来创建数据库。当我添加迁移和更新数据库时,它总是默认为 table 中的每个外键创建 Non-cluster Index

我的问题: EF6 是否有任何全局设置不在外键上创建 Non-Cluster index

我已经搜索并找到了以下解决方案

Solution 1: Remove index line from migration before updating database

解决方案 1 不适合我,因为我有很多 table 并且我的 db 已经创建。手动删除索引创建行需要很多时间。

此外,我也在使用 fluent api 是否有任何与此问题相关的选项?

我认为对此没有简单的解决方案,但我知道您可以做什么:创建自定义迁移生成器。

迁移生成器是负责从迁移代码文件创建数据库 运行 的 SQL 脚本的组件。根据屏幕截图,我假设您有 SQL 服务器。在这种情况下,您可以编写一个自定义 sql 生成器来简单地覆盖索引创建操作,这样如果索引是非聚集的,则不会向脚本写入任何内容:

public class NoIndexGenerator : SqlServerMigrationSqlGenerator
{
  protected override void Generate(CreateIndexOperation createIndexOperation)
  {
    if (!createIndexOperation.IsClustered)
    {
      return;
    }
  }
}

然后你必须在迁移的 Configuration class 中注册这个组件:

internal sealed class Configuration : DbMigrationsConfiguration<MyCtx>
{
  public Configuration()
  {
    AutomaticMigrationsEnabled = false;

    // Add this line to register the sql generator
    this.SetSqlGenerator("System.Data.SqlClient", new NoIndexGenerator());
  }
}

现在,如果您 运行 Add-Migration,您将拥有一个正常的迁移文件,其中包含 CreateIndexOperation。但是如果你 运行 Update-Database,将不会创建非聚集索引。如果你 运行 Update-Database -Script,你也可以检查这个。生成的脚本没有非聚集索引。

如果需要,您可以在管道中走得更高,并创建自定义 C# 迁移脚手架。它应用与 sql 生成器相同的逻辑:

internal class NoIndexMigrationCodeGenerator : CSharpMigrationCodeGenerator
{
  protected override void Generate(CreateIndexOperation createIndexOperation, IndentedTextWriter writer)
  {
    if (!createIndexOperation.IsClustered)
    {
      return;
    }
  }
}

然后,你可以像这样在Configuration class中注册它:

internal sealed class Configuration : DbMigrationsConfiguration<MyCtx>
{
  public Configuration()
  {
    AutomaticMigrationsEnabled = false;

    // Add this line to register the C# code generator
    this.CodeGenerator = new NoIndexMigrationCodeGenerator();
  }
}

现在,如果您 运行 Add-Migration,CreateIndex 操作也会从生成的迁移 cs 文件中消失。

我可能会选择第二种解决方案(这可能会让阅读您的代码的其他人感到困惑,因为他们看到迁移 cs 文件中有 CreateIndex 操作,但 SQL 脚本中没有),但是最终是你的选择:)

如果需要,您可以使用 Generate() 方法的 createIndexOperation 参数的其他属性来实现更复杂的索引过滤。

如果需要,您还可以重写参数类型为 DropCreateIndexOperation 的生成方法,但由于索引是使用 'drop-if-exists' 模式删除的,因此我认为没有必要这样做。

编辑

虽然上面的代码示例似乎有效,但为了公平和遵循一般的最佳实践和原则,您可能应该在 if 语句之后包括对两个生成器中的基本方法的调用。

嗯,我认为这可能是一种 'If all you have is a hammer...' 的情况。

我之前给出的答案有效(我支持它,因为它非常有趣而且很棒),但这可能不是最好的方法。

最近我检查了所有 EF 使用生成数据库的默认约定,其中有一个负责在 FK-s 上生成非聚集索引。只需完全删除该约定,问题就解决了:

 protected override void OnModelCreating(DbModelBuilder modelBuilder)
 {
   base.OnModelCreating(modelBuilder);

   // using System.Data.Entity.ModelConfiguration.Conventions;
   modelBuilder.Conventions.Remove<ForeignKeyIndexConvention>();
 }

在尝试使用 CSharpMigrationCodeGenerator 之后,我开始考虑如何覆盖 ForeignKeyIndexConvention。 因此,我实施了允许跳过在主键列上添加索引的检查,假设未覆盖 PK 命名约定。

public class ForeignKeyIndexConventionFix : ForeignKeyIndexConvention
{
    private const string Id = "Id";

    public override void Apply(AssociationType item, DbModel model)
    {
        if (item == null)
        {
            throw new ArgumentNullException(nameof(item));
        }
        if (item.Constraint == null)
        {
            return;
        }
        if (item.IsForeignKey)
        {
            if (IsPrimaryKeyColumn(item.Constraint))
            {
                return;
            }                
        }
        base.Apply(item, model);
    }

    private static bool IsPrimaryKeyColumn(ReferentialConstraint constraint)
    {
        IEnumerable<string> dependentColumns = constraint.ToProperties.Select(p => p.Name);

        if (dependentColumns.Count() == 1)
        {
            string dependentColum = dependentColumns.First();

            if (dependentColum.Equals(Id, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
        }
        return false;
    }
}

然后覆盖你的 DbContext class:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  base.OnModelCreating(modelBuilder);

  modelBuilder.Conventions.Remove<ForeignKeyIndexConvention>();
  modelBuilder.Conventions.Add<ForeignKeyIndexConventionFix>();
}

调试解决方案时出现问题 - Console.WriteDebug.Write 没有给出输出 - 所以我只是跟踪到临时位置的某个文本文件。也许有更好的方法..?

原始实现的代码源有助于弄清楚如何获取依赖列名称:https://github.com/dotnet/ef6/blob/master/src/EntityFramework/ModelConfiguration/Conventions/Edm/Db/ForeignKeyIndexConvention.cs

对于 EF Core 6.0,您需要从包含它的所有约定集中删除 ForeignKeyIndexConvention。这是一个可行的解决方案:

创建一个新的 ConventionSetBuilder class:

public class CustomSqlServerConventionSetBuilder : SqlServerConventionSetBuilder, IConventionSetBuilder
{
    public CustomSqlServerConventionSetBuilder(ProviderConventionSetBuilderDependencies dependencies, RelationalConventionSetBuilderDependencies relationalDependencies,
        ISqlGenerationHelper sqlGenerationHelper) : base(dependencies, relationalDependencies, sqlGenerationHelper)
    {
    }

    public override ConventionSet CreateConventionSet()
    {
        var cs = base.CreateConventionSet();

        //ForeignKeyAddedConventions
        var foreignKeyAddedConvention = cs.ForeignKeyAddedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (foreignKeyAddedConvention != null)
            cs.ForeignKeyAddedConventions.Remove(foreignKeyAddedConvention);

        //ForeignKeyRemovedConventions
        var foreignKeyRemovedConventions = cs.ForeignKeyRemovedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (foreignKeyRemovedConventions != null)
            cs.ForeignKeyRemovedConventions.Remove(foreignKeyRemovedConventions);

        //EntityTypeBaseTypeChangedConventions
        var entityTypeBaseTypeChangedConventions = cs.EntityTypeBaseTypeChangedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (entityTypeBaseTypeChangedConventions != null)
            cs.EntityTypeBaseTypeChangedConventions.Remove(entityTypeBaseTypeChangedConventions);

        //KeyAddedConventions
        var keyAddedConventions = cs.KeyAddedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (keyAddedConventions != null)
            cs.KeyAddedConventions.Remove(keyAddedConventions);

        //KeyRemovedConventions
        var keyRemovedConventions = cs.KeyRemovedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (keyRemovedConventions != null)
            cs.KeyRemovedConventions.Remove(keyRemovedConventions);

        //ForeignKeyPropertiesChangedConventions
        var foreignKeyPropertiesChangedConventions = cs.ForeignKeyPropertiesChangedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (foreignKeyPropertiesChangedConventions != null)
            cs.ForeignKeyPropertiesChangedConventions.Remove(foreignKeyPropertiesChangedConventions);

        //ForeignKeyUniquenessChangedConventions
        var foreignKeyUniquenessChangedConventions = cs.ForeignKeyUniquenessChangedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (foreignKeyUniquenessChangedConventions != null)
            cs.ForeignKeyUniquenessChangedConventions.Remove(foreignKeyUniquenessChangedConventions);

        //IndexAddedConventions
        var indexAddedConventions = cs.IndexAddedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (indexAddedConventions != null)
            cs.IndexAddedConventions.Remove(indexAddedConventions);

        //IndexRemovedConventions
        var indexRemovedConventions = cs.IndexRemovedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (indexRemovedConventions != null)
            cs.IndexRemovedConventions.Remove(indexRemovedConventions);

        //IndexUniquenessChangedConventions
        var indexUniquenessChangedConventions = cs.IndexUniquenessChangedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (indexUniquenessChangedConventions != null)
            cs.IndexUniquenessChangedConventions.Remove(indexUniquenessChangedConventions);

        //ModelFinalizingConventions
        var modelFinalizingConventions = cs.ModelFinalizingConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
        if (modelFinalizingConventions != null)
            cs.ModelFinalizingConventions.Remove(modelFinalizingConventions);
        return cs;
    }
}

并在您的 DbContext 配置中替换 ConventionSetBuilder:

public partial class YourDbContext : DbContext
{
    ...

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        ...
        optionsBuilder.ReplaceService<IConventionSetBuilder, CustomSqlServerConventionSetBuilder>();
    }
}