C# 在急切地加载include语句时,如何处理实体框架的缓慢性能?

C# 在急切地加载include语句时,如何处理实体框架的缓慢性能?,c#,asp.net-mvc,entity-framework,C#,Asp.net Mvc,Entity Framework,我试图使用实体框架从SQL Server数据库中提取大量关系,以便在摘要网页上显示,我发现在查询中使用许多include语句的性能非常糟糕 要求是一次在一个页面上显示单个用户的所有数据,一般来说,这不是一个巨大的数据量,但是获取它确实需要使用类似这样的查询遍历相当多的EF关系 var class = context.Class.Where(a => a.Id.Equals(Id)) .Include(a => a.Teacher.Addre

我试图使用实体框架从SQL Server数据库中提取大量关系,以便在摘要网页上显示,我发现在查询中使用许多include语句的性能非常糟糕

要求是一次在一个页面上显示单个用户的所有数据,一般来说,这不是一个巨大的数据量,但是获取它确实需要使用类似这样的查询遍历相当多的EF关系

var class = context.Class.Where(a => a.Id.Equals(Id))
                      .Include(a => a.Teacher.Address)
                      .Include(a => a.Teacher.Supplies.Notebooks)
                      .Include(a => a.Teacher.Supplies.Pencils)
                      .Include(a => a.Teacher.Supplies.Textbooks)
                      .Include(a => a.Teacher.Supplies.Erasers)
                      .Include(a => a.Students.Select(d => d.Supplies.Notebooks))
                      .Include(a => a.Students.Select(d => d.Supplies.Pencils))
                      .Include(a => a.Students.Select(d => d.Supplies.Textbooks))
                      .Include(a => a.Students.Select(d => d.Supplies.Erasers))
                      .Include(a => a.Configuration)
                      .Include(a => a.Payment.Payer.Address)
                      .Include(a => a.Payment.PaymentMethod)
                      .First();
在包含最少数据的测试数据库上运行需要10秒以上。但是,如果改为执行此操作,则性能需要约1秒:

var class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Teacher.Address).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Teacher.Supplies.Notebooks).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Teacher.Supplies.Pencils).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Teacher.Supplies.Textbooks).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Teacher.Supplies.Erasers).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Students.Select(d => d.Supplies.Notebooks)).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Students.Select(d => d.Supplies.Pencils)).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Students.Select(d => d.Supplies.Textbooks)).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Students.Select(d => d.Supplies.Erasers)).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Configuration).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Payment.Payer.Address).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Payment.PaymentMethod).First();

这真的是运行查询以获取所有这些数据的最佳方法吗?还是我完全错了

好吧,我数了18个连接,所以无论如何都不会太快。然而,取决于你测试的方式,你的结果可能有意义,也可能没有意义。特别是,如果您正在调试或者只是在本地运行,那么总体来说,一切都会变慢。如果您使用的是LocalDb,它将比SQL Server慢。IIS Express是单线程的,而IIS是多线程的,因此这对性能也有影响

首先,也是最重要的一点,你应该做一些类似于运行你的项目的事情。这将使您实际上将加载页面所需的时间与运行查询所需的时间分开,并使您能够了解正在运行的查询的数量和范围。然而,除非您在一台实际的生产机器上进行测试,使用完整的IIS和SQL Server,否则您不会真正知道这将如何执行


如果这是一个真正的问题,您可以考虑创建一个存储过程来返回所有这些信息。这将比EF所能做的任何事情都要快,因为SQLServer可以存储执行计划并优化查询。如果您发现实体框架对于您的目的来说太慢,一般来说,您也可以使用类似的替代orm进行研究。当然,你会有一个学习曲线,但如果你主要关注的是性能,你几乎总是可以比实体框架做得更好,尽管你可能会在这个过程中失去一些细节。

不幸的是,我无法解释为什么
Include
技术如此缓慢,但我会尝试这样获取数据:

var class = context
    .Class
    .Where(a => a.Id.Equals(Id))
    .First()
.Select(a => new 
{
    TA = a.Teacher.Address,
    TN = a.Teacher.Supplies.Notebooks,
    TP =a.Teacher.Supplies.Pencils,
    TT = a.Teacher.Supplies.Textbooks,
    TE = a.Teacher.Supplies.Erasers,
    SN = a.Students.Select(d => d.Supplies.Notebooks),
    SP = a.Students.Select(d => d.Supplies.Pencils),
    ST = a.Students.Select(d => d.Supplies.Textbooks),
    SE = a.Students.Select(d => d.Supplies.Erasers),
    a.Configuration,
    PA = a.Payment.Payer.Address,
    a.Payment.PaymentMethod
};

这会提高性能吗?如果是这样的话,我会对这两种技术生成的sql之间的差异感兴趣

我实际上是在网络上使用sql server的完整版本,就像在prod中一样。我还通过测试用例而不是通过IIS运行查询。我只是惊讶于它的搜索速度有多慢,因为要搜索的数据太少了,我担心当有更多的数据时它会如何运行。好吧,就像我说的,有这么多的连接,最好使用存储过程。EF不仅在连接方面效率不高,而且必须具体化整个对象图,这需要一段时间。存储过程将返回一个平坦的结构,您可以将其与一个伴生的DTO类一起使用,以获得更简单的结构和初始化。如果您需要灵活地动态执行这些连接,那么您可能必须转向更高效的ORM,如Dapper。我同意SP会更快,但我更愿意避免这种方式。它不需要闪电般的速度,只要足够快,可以在合理的时间内显示网页。我觉得奇怪的是,第二个版本要快得多,因为EF必须单独执行每个查询,然后组装结果本身。我现在又实现了两个测试,它们按顺序获取多个结果,而不仅仅是单个结果。有趣的是,只有第一个结果是慢的,而后续的调用是毫秒。当查询在同一测试中运行时,它必须缓存查询。希望这将转化为web环境,并且在web服务器启动后,查询只会在第一次运行时变慢。我不知道查询是否会无限期地缓存,但我想这是另一个问题。单个查询速度更快,因为对连接有指数效应。在单个查询中执行的联接越多,整个查询的运行速度就越慢。SQL Server必须更加努力地制定一个包含18个连接的执行计划,而不是一个包含2-3个连接的执行计划。就存储过程而言,您真的不应该将其视为一种惩罚。存储过程非常棒,当您真正开始使用时,它们实际上更容易使用。长话短说,EF并不适合这种类型的工作,因此,您要么处理缓慢的问题,要么使用其他方法。您是使用代码优先还是数据库优先模型?这是代码优先模型。使用视图是一个选项,但在使用代码优先时实现起来并不容易。这是返回一个匿名类型,而不是不理想的类对象。我无论如何都实现了它来评估性能,但是它和include版本一样慢。EF必须以同样的方式评估它。它实际上比原来的更糟糕。在这种形式下,您将生成大量查询,因为所有内容都是有效的延迟加载。我计算了22个查询,不包括初始值。@ChrisPratt,EF肯定会在这里生成一个查询吗?。也许你的意思是它会生成几个子查询?。我不明白的是,为什么zacs第二种技术相对有效,但第一种技术生成1个查询的速度要慢10倍多。我发现很难相信这种差异仅仅是SQL Server更努力地制定了一个包含8个以上连接的执行计划,而不是12个包含2-3个连接的执行计划。First()是可评估的。因此,在开始选择此处的所有其他内容之前,查询已经发送。然后,延迟加载每个附加关系,每次加载一个分支。更糟的是,因为你从来没有