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.Write
和 Debug.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>();
}
}
我正在使用 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.Write
和 Debug.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>();
}
}