禁用延迟加载将防止 Select N+1 性能问题以及递归序列化 bail-outs,但是它用另一个工件空引用替换了这些问题。
在使用 Web 应用程序时,我不会禁用延迟加载,而是确保我的控制器/API不返回实体,而是返回 ViewModel 或 DTO。当您采用 POCO 类来为您的视图提供数据量和它们所需的结构,并使用 .Select() 或 Automapper 的 ProjectTo<TViewModel>() 通过延迟执行来填充它们时,您无需担心延迟加载,并且为您的应用程序引入更好的整体性能和资源使用。从技术上讲,使用这种方法,可以禁用延迟加载,因此实际上并不是支持或反对禁用它,而是仅仅禁用延迟加载的行为不会让您的 Web 应用程序“更好”。
采用 ViewModel 具有许多优势:
- 避免延迟加载调用或意外的 #null 引用。
- 仅发送视图/消费者所需的数据,仅此而已。 (通过网络传输的数据更少,为具有调试视图的黑客提供的信息也更少。)
- 为数据库服务器构建高效、可索引的查询。
- 为不会触发 EF 的计算列提供一个位置。
- 有助于减少返回途中的安全问题(意外的实体修改)。 (懒惰的开发人员不能简单地将视图模型重新附加并提交到 DB)
所以对我来说,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)需要从数据中获得什么。