如何阻止EF Core索引所有外键

11
Entity Framework Indexing ALL foreign key columns这类问题所述,EF Core似乎自动生成了每个外键的索引。对我来说,这是一个合理的默认设置(不要在这里进行观点战......),但有些情况下它只是浪费空间并减缓插入和更新操作的速度。 我如何在单独的情况下禁用它?
我不想完全关闭它,因为它做的好处大于坏处;我也不希望手动配置所有我想要的索引。我只想防止在特定的FK上自动生成索引。
相关问题:这些自动生成的索引的事实是否在EF文档中提到过?我无处可找到它,这可能就是为什么我找不到如何禁用它的原因? 由于在保存时间方面,肯定会有人质疑我为什么要这样做......所以链接问题的OP在评论中给出了一个很好的例子:

例如,我们有一个People表和一个Addresses表。 EF对People.AddressID FK进行了索引,但我始终从People行开始,并搜索Addresses记录;我从不在Addresses行中查找,然后在People.AddressID列中搜索匹配的记录。


如果只是针对几个特定的表,你不能只从迁移中删除添加索引的代码吗? - Valuator
@Valuator 抱歉,我有点迷失了。什么迁移?当我的应用程序启动时,它通过调用 db.Database.EnsureCreated(); 从头开始创建数据库。据我所知,索引的创建是在幕后自动完成的。顺便说一下,这是 EF Core 2。 - pbristow
1
此外,我建议阅读以下关于EnsureCreated()的MS文档。如果您有一些模型更改,它不会更新您的数据库 - 迁移会做到这一点。即使是针对EF7,也很有趣的是EF7 EnsureCreated vs. Migrate Methods。在这里,您可以找到使用所有支持方法的EF和EF Core教程。 - ChW
@ChW 谢谢。如果您查看我的示例以及链接,您将看到真实世界的、非平凡的例子,在这些索引提供零实际好处,并且仅会拖慢应用程序。我在我的 EF Core 应用程序中有表格有 6 个索引,其中有 4 个永远不会被使用,并且这些索引占据的空间比数据本身多了 50%。这些索引意味着一个插入操作接触到 7 个磁盘页面,而它只需接触 3 个。我没有在评论中争论这个问题的意愿;这是一个事实,而不是观点,即此功能并不总是有用的。如果必须要辩论这个问题,请在其他问题上进行。谢谢。 - pbristow
1
@pbarranis 好的。我会使用迁移并自己管理索引,并删除不需要的索引。希望能找到一个可接受的答案,因为我也很感兴趣。 :) - ChW
显示剩余3条评论
4个回答

4

那是很多工作,但总比没有好。谢谢! - pbristow
这在EF Core中似乎不起作用。有什么想法现在该怎么做? - pinkfloydx33
1
你的回答指引了我正确的方向,但现在代码要简单得多。只需将以下代码添加到你的DBContext类中: protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention)); } - GuyVdN

1
如果真的有必要避免在.Net Core中使用某些外键索引,据我所知,需要删除设置索引的代码以生成迁移代码文件。另一种方法是实现自定义迁移生成器,结合属性或扩展方法来避免索引创建。你可以在EF6的这个答案中找到更多信息:EF6 preventing not to create Index on Foreign Key。但我不确定它在.Net Core中是否有效。这种方法似乎有点不同,这里是一个MS doc article,应该会有帮助。但是,我强烈建议不要这样做!我反对这样做,因为你必须修改生成的迁移文件,而不是因为不使用FK的索引。像你在问题的评论中提到的那样,在实际情况下,有些情况需要这种方法。

对于其他人来说,他们不确定是否需要避免在FK上使用索引,因此必须修改迁移文件:

在这种情况之前,我建议在FK上实现应用程序并检查性能和空间使用情况。因此,我会生成大量测试数据。 如果在测试或QA阶段确实导致性能和空间使用问题,则仍然可以在迁移文件中删除索引。

因为我们已经讨论了EnsureCreatedmigrations的区别,为了完整起见,以下是有关EnsureCreated和migrations的更多信息(即使您不需要它 :-)):


谢谢。我同意这样做会非常麻烦、容易出错,而且完全是个坏主意。我绝对不会走这条路,但我的数据库很小,性能影响也很小。可悲的是,我花了更多时间研究如何解决这个问题,而不是检查我的整个模式并列出所有不必要的FK索引以进行删除(可能有50-100个)。嗯。 - pbristow
当前的设计有变化吗?我注意到正在创建的索引被设置为唯一的。如果该索引是用于外键,那么默认值不应该是非唯一的吗? - Rob White
@RobWhite 我知道你的评论已经有些年头了,但或许其他人也会发现这很有帮助——只有在你的外键定义了一对一关系时,索引才被设置为唯一。 - Marchyello

0

Entity Framework Core 2.0(问题提出时的最新版本)没有这样的机制,但是EF Core 2.2可能会有 - 以Owned Entity Types的形式。

因为你说:

"我只从People行开始并搜索Addresses记录;我从不找到一个Addresses行"

那么,您可能希望将Address作为Owned Entity Type(尤其是使用'在单独的表中存储所拥有类型'的变体,以匹配您选择将地址信息存储在单独的Addresses表中)。该功能的文档似乎说了一句匹配的话:

"所拥有的实体基本上是所有者的一部分,不能没有它而存在"

顺便说一下,现在该特性已经在 EF 中,这可能可以解释为什么 EF 总是为 HasMany/HasOne 创建索引。这很可能是因为 Has* 关系应该用于其他实体(而不是“值对象”),由于它们具有自己的标识,因此意味着可以独立查询它们,并使用导航属性访问它们相关联的其他实体。但对于这种用例来说,如果没有索引,使用这些导航属性将非常危险(少数查询可能会导致数据库大幅减速)。
然而,在这里有一些要注意的地方:
将一个实体转换为拥有的实体并不仅仅是指示 EF 关于索引的事项,而是指示以一种略微不同的方式将模型映射到数据库中(有关详细信息,请参见下文),但最终效果实际上不包含在 People 上的额外索引。
但很有可能,这实际上可能是更好的解决方案:这样你也可以说没有人应该查询地址(通过不允许创建那种类型的DbSet<T>),从而最大程度地减少了有人使用它来访问其他实体的机会,以及这些昂贵的无索引查询。
关于它们的区别,你会注意到如果你让Address属于Person,EF会在Address表中创建一个PersonId列,这与People表中的AddressId不同(从某种意义上说,缺少外键有点欺骗性:查询地址的人的索引是存在的,只是它是People表的主键索引,本来就存在)。但请注意,这个设计实际上相当好——它不仅需要少一列(People中没有AddressId),而且还保证了没有办法制作孤立的Address记录,你的代码永远无法访问。
如果你仍然想在Addresses中保留AddressId列,那么还有一个选项:
  • Addresses表中为外键选择一个名称AddressId,并且“假装”你不知道它恰好具有与PersonId相同的值 :)

如果这个选项不好玩(例如因为您无法更改数据库模式),那么您可能会有些倒霉。但请注意,在EF的当前缺点中,他们仍然列出了“拥有实体类型的实例不能由多个所有者共享”,而以前版本的一些缺点已经被列为已解决。也许值得关注这个领域,因为在我看来,解决这个问题可能涉及到在People中拥有您的AddressId的能力,因为在这样的模型中,为了使拥有的对象在多个实体之间共享,外键需要与拥有实体一起放置,以为每个实体创建对相同值的关联。


0
在 OnModelCreating 重写中,在调用之后。
base.OnModelCreating(modelBuilder);

添加:

var indexForRemoval = modelBuilder.Entity<You_Table_Entity>().HasIndex(x => x.Column_Index_Is_On).Metadata;
modelBuilder.Entity<You_Table_Entity>().Metadata.RemoveIndex(indexForRemoval);

'''


很遗憾,这个方法不起作用。迁移文件中仍然存在索引。 - Luke Vo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接