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

时间:2017-09-14 07:17:53

标签: entity-framework ef-fluent-api

我使用EF6代码第一种方法来创建数据库。当我添加迁移和更新数据库时,它默认为表中的每个外键创建Non-cluster Index

enter image description here

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

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

Solution 1: Remove index line from migration before updating database

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

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

3 个答案:

答案 0 :(得分:4)

我不相信有一个简单的解决方案,但我知道你可以做些什么:创建一个自定义迁移生成器。

迁移生成器是负责从迁移代码文件创建在数据库上运行的SQL脚本的组件。我假设你有基于截图的SQL Server。在这种情况下,您可以编写一个自定义的sql生成器,它只是覆盖索引创建操作,这样如果索引是非群集的,则不会向脚本写入任何内容:

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

然后,您必须在迁移的Configuration类中注册此组件:

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类中注册,如下所示:

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脚本中没有),但最终它是你的选择:)

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

如果需要,您还可以覆盖具有DropCreateIndexOperation类型参数的Generate方法,但是因为索引会被删除,如果存在&#39; drop-if-exists&#39;模式,我不认为这是必要的。

修改

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

答案 1 :(得分:4)

嗯,我认为这可能是'如果你拥有的只是一把锤......'那种情况。

我之前给出的答案是有效的(我支持它,因为它非常有趣和令人敬畏),但它可能不是最好的方法。

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

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

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

答案 2 :(得分:0)

尝试使用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类:

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