【发布时间】:2013-08-08 14:15:07
【问题描述】:
[更新 - 见底部更新]
我使用的是 EF 代码优先,并且通常对此感到满意。但是,一个简单(且常见)的操作会导致 EF 生成异常复杂的 SQL,这会降低我的应用程序的速度。
我只是使用(整数)ID 列表获取一组实体,但因为我需要许多子实体的详细信息,所以我使用 .Include() 来同时加载这些子实体,如下:
db.MyEntities
.Where(x => x.ClientId == clientId)
.Where(x => ids.Contains(x.Id))
.Where(x => x.SubEntity1 != null)
.Include(x => x.SubEntity1)
.Include(x => x.SubEntity1.SubSubEntity1)
.Include(x => x.SubEntity1.SubSubEntity2)
.Include(x => x.SubEntity1.SubSubEntity3)
.Include(x => x.SubEntity1.SubSubEntity4)
.Include(x => x.SubEntity2)
.Include(x => x.SubEntity2.SubSubEntity1)
.Include(x => x.SubEntity2.SubSubEntity2)
.Include(x => x.SubEntity2.SubSubEntity3)
.Include(x => x.SubEntity2.SubSubEntity4)
.Include(x => x.SubEntity3)
如您所见,这不是一个特别复杂的查询,除了所有Includes 之外。
EF 为此生成的 SQL 巨大 - 大约 74Kb 的 SQL。执行并不需要很长时间(因为通常 ID 列表中的项目数量很少),但 EF 仅仅构造查询需要一秒钟以上 - 即在查询之前甚至发送到数据库。
如果我删除Includes,那么查询会小得多,整个事情花费的时间也少得多 - 但是各种相关实体会一次加载一个,这不能很好地扩展。
EF 似乎给了我两个加载数据的选项:
- 在初始查询期间一次加载所有子实体(如上使用
Include),或 - 一次加载一个子实体(使用延迟加载,或显式使用
Load/LoadProperty)。
如果选项 1 有效,那将是我的首选选项,但由于在这种情况下不起作用,我唯一剩下的选项是 2 - 我认为这是不可接受的:将有太多的数据库查询输入的 ID 列表(即实体的数量)很大。
在我看来,EF 似乎没有解决另一个选项:获取主要实体,获取所有相关的 SubEntity1 实体,然后获取所有相关的 SubEntity2 实体,等等。这样,查询的数量与要获取的实体的 types 的数量有关,而不是与实体的数量有关。这会更好地扩展。
我在 EF 中看不到这样做的方法:换句话说,就是说“为所有这些实体加载此属性(在单个查询中)”。
我是否只能放弃 EF 并编写自己的 SQL?
更新
我注意到即使我删除了Includes,生成的 SQL 也比我认为的要复杂,我认为这一切都源于 EF 不“喜欢”我的表结构这一事实。几天来,我一直在努力让 EF 通过 Code First(和 Fluent API)创建我正在寻找的数据库结构,即使我(几乎)到达了我想要的位置,我也不得不接受一些妥协。
我想我现在因为敢于做 EF 不希望我做的事情而付出更多的惩罚。看起来一个简单的查询比它应该的更复杂,一个稍微复杂的查询要复杂得多。
这令人难以置信的沮丧 - 我以为我已经把所有这些 EF 麻烦抛在脑后了,现在系统正在生产中,有几十个用户 - 这让我很难重新开始。
看来我将不得不花费永恒的时间与 EF 的牙齿和指甲战斗。我多么希望我一开始就没有使用它!
无论如何,回到我最初的问题:如果我有一堆 A 类型的实体,我想在一个查询中加载 B 类型的相关子实体,有没有办法这样做?
【问题讨论】:
-
为什么查询量这么大?有什么意想不到的构造吗?还是只是 SELECT 中的字段列表有一英里长?
-
@GertArnold:好问题,是的,SELECT 中有很多字段。当然,各种子实体表有很多 JOIN。但是,似乎存在大量嵌套语句,并且重复 JOIN-ing 相同的表。如果我自己构建查询,这远远超出了我的需要。
-
您的应用程序是否面向 .NET 4.5?或者至少在运行应用程序的机器上安装了 .NET 4.5?在这种情况下,您应该注意到仅在第一次运行查询时有超过 1 秒的延迟,因为 EF 5 在将 .NET 4.5 转换为 SQL 查询后缓存了已转换的 LINQ 查询。
-
@Slauma:我有 4.5,但查询似乎没有被缓存(或者更确切地说,缓存的查询没有被命中)。查看 SQL,它有一些参数,还有一些硬编码的 ID,所以我认为随着 ID 的变化,查询也会发生变化。
-
我明白了。你能用局部变量替换 LINQ 查询中的硬编码值吗?它们应该被翻译成 sql 参数。
标签: entity-framework ef-code-first entity-framework-5