我听说你必须在Web应用程序中禁用EF的延迟加载功能。 (ASP.NET)。例如,Here和here。
现在我真的很困惑,因为我一直认为应该始终启用延迟加载。所以现在我的问题是:在性能方面禁用Web应用程序中的延迟加载真的是一个更好的主意。如果是的话,有人可以解释原因并提及利弊吗?
在Web应用程序中,您有来自不同用户的大量并发请求,每个请求的处理速度都非常快(至少应该是这样),因此您希望在每个请求期间减少数据库调用,因为每个数据库请求都是通过网络发生的。使用延迟加载时,每次使用关系属性时,它都会再次调用DB以将此相关数据加载到实体集合中。因此,在一个http请求期间,您可以通过这种方式向DB提出大量额外请求,性能预期会对您的应用程序产生什么影响。在正常情况下,当您最初从数据库中获取数据时,您已经知道需要哪些相关数据,因此您可以使用热切加载相关实体来将一个请求中所需的所有内容加载到数据库以处理特定的http请求。
禁用延迟加载将阻止选择N + 1性能问题以及递归序列化纾困,但是它会替换另一个工件,即空引用。
使用Web应用程序时,我不会禁用延迟加载,而是确保我的控制器/ API不返回实体,而是返回ViewModels或DTO。当您采用POCO类来为视图提供数据量和所需结构时,并使用.Select()
或Automapper的ProjectTo<TViewModel>()
通过延迟执行来填充它们,您可以避免担心延迟加载,并引入更好的整体性能和应用程序的资源使用情况。从技术上讲,使用这种方法,可以禁用延迟加载,因此它不是支持或禁止禁用它的参数,而是仅仅禁用延迟加载的行为不会使您的Web应用程序“更好”。
采用ViewModels具有许多优点:
所以对我来说,Web应用程序的问题不是延迟加载,而是简单地避免将实体传递给客户端。我看到很多,他们传递实体的“例子”太多了。我认为这不是一个健康的模式。
例如,使用视图模型的第一个问题是“我的视图实际需要哪些数据?”所以考虑到产品和产品类别,如果我想发送产品实体,但我也需要产品类别名称,例如,如果我们的产品类别包含一系列产品,那么我们就会遇到一个令人讨厌的问题,并且每个产品都有一个类别。当我们将产品传递给序列化程序时,它将触及该循环引用,它要么挽救(留下引用或集合#null),否则它将抛出异常。但是通过Select N + 1,我们将迭代Product属性,点击ProductCategory引用,然后“SELECT FROM ProductCategory WHERE ProductCategoryID = 3”。然后,当我们遍历该产品类别时,我们会触及另一个引用,这就是另一个SELECT ....然后在链中。
通过使用视图模型,您可以将要检索的数据限制为视图所需的内容。我创建了一个产品视图模型,它概述了我关心的领域,无论数据来自何处。如果我想要一个像产品,它的名字,它的类别名称:
public class ProductViewModel
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string CategoryName { get; set; }
}
然后加载它:
var viewModel = context.Products
.Where(x => x.ProductId == productId)
.Select(x => new ProductViewModel
{
ProductId = x.ProductId,
ProductName = x.Name,
CategoryName = x.Category.Name
}).Single();
完成。不需要延迟加载或急切加载。这会生成一个查询,返回一个只包含我们需要的3列的记录。 (而不是整个产品记录,它的产品类别)
随着需求变得越来越复杂,我们可以引入视图模型层次结构,但我们可以根据视图实际需要的内容继续压缩相关数据。在某些情况下,它可能意味着选择一个匿名类型,然后将这些结果转换为视图模型,我们需要使用EF无法转换为SQL的函数等。该方法是依赖加载实体的强大而快速的替代方法,但在开始时需要注意了解数据所需的最终消费者(视图/ API)。
我有一个遗留项目。当我在sql配置文件中检查有多少请求进入数据库时,我真的很惊讶。主页大概是180(!)!主页只有两个列表,每个列表包含20-30个项目。所以,你应该很好地理解N + 1个请求。你应该仔细检查代码审查它。对我来说,延迟加载会带来很多问题。使用该功能时,您永远不知道有多少请求进入数据库。
延迟加载会产生N + 1问题。
这是什么意思?
这意味着它将为每个加载的对象+初始查询一次(至少)命中DB。
为什么不好?
假设你有这些类Movie
和MovieGenre
,你在它们之间有100部电影,它们之间有30种类型
public class Movie
{
public int Id { get; set; }
public string Name { get; set; }
public virtual MovieGenre MovieGenre { get; set; }
public byte MovieGenreId { get; set; }
}
public class MovieGenre
{
public byte Id { get; set; }
public string Name { get; set; }
}
现在假设您正在导航到一个将显示所有100部电影的页面(请记住30部电影类型?),数据库将执行31个查询(每个电影类型30个,电影类型1个),查询将是这样的
初始查询(+1部分):
SELECT Id, Name, MovieGenreId
From Movie
附加查询(N部分): - 1个SELECT Id,来自MovieGenre的名称,其中Id = 1
-- 2
SELECT Id, Name
From MovieGenre
Where Id = 2
-- 3
SELECT Id, Name
From MovieGenre
Where Id = 3
-- 4
SELECT Id, Name
From MovieGenre
Where Id = 4
.
.
.
急切加载避免了所有这些混乱,并将使用一个查询与正确的连接。
因为你正在使用C#,所以你可能想用a tool called glimpse来进一步理解这个问题。
我认为你需要首先提出几个问题,然后再选择一个并拒绝其他方式:
我对其他人的所有重要投入的贡献。
我想说,延迟加载肯定会帮助用户在很少使用时不取数据。
想象您在Web应用程序中拥有Master => Details方案,当您意识到用户对细节不太感兴趣时。 (您可以分析和审核用户发起的请求)。不需要预先为每个主记录加载详细信息。
另一方面,如果细节是与之交互的主要部分,只需进行预先加载并获取整个Master => Detail for each。
除了懒惰/急切加载,请确保以下内容:
始终在服务器端启用分页并加载有限数据/在用户请求逐页导航时加载数据。
如果主要有读取请求,请禁用自动跟踪。