首页 > 解决方案 > 防止 EF 包含相关实体

问题描述

我有一个 SQL Server 数据库表,其中有几百万条记录。我有一个 MVC 站点,其中有一个页面来显示该表中的数据,并且我遇到了广泛的性能问题。

像这样运行一个简单的查询大约需要 25-30 秒才能返回大约 2000 行:

_dbContext.Contracts
    .Where(c => c.VendorID == vendorId)
    .ToList();

当我对数据库运行查询时,只需要几秒钟。

事实证明,EF 正在为 my 加载所有相关实体Contract,因此大大减慢了我的查询速度。

在调试器中,返回的对象是一种奇怪的类型,不确定这是否是一个问题:

System.Data.Entity.DynamicProxies.Contract_3EF6BECBB56F2ADDDA6E0050AC82D03A4E993CEDF4FCA49244D3EE4005572C46

与我的相关实体相同Contract

System.Data.Entity.DynamicProxies.Vendor_4FB727808BD6E0BF3B25085B40F3F0B9B10EE4BD17D2A4C600214634F494DB66

该站点有点旧,它是带有 EF 4 的 MVC 3。我知道在当前版本的 EF 上,我必须明确使用Include()来获取相关实体,但这里似乎是自动包含的。

我有一个 EDMX 文件,其中有一个 .tt 文件和实体类,但我没有看到任何可以阻止我的课程获取相关对象的地方。

我有什么办法吗?

标签: c#asp.netentity-framework

解决方案


如果您的 MVC 控制器将实体返回到视图,那么您遇到的陷阱是序列化程序正在迭代返回的实体并延迟加载所有相关数据。这比触发急切加载要糟糕得多,因为在加载集合的情况下,这将一次获取相关实体/集合一个父项。

假设我获取了 100 个合同,并且合同包含供应商参考。

渴望加载我会使用:

context.Contracts.Where(x => /* condition */).Include(x => x.Vendor).ToList();

这将组成 1 个查询,加载所有适用的合同及其供应商详细信息。但是,如果您让序列化程序延迟加载供应商,您将有效地获得以下结果:

context.Contracts.Where(x => /* condition */).ToList(); // gets applicable contracts...
// This happens behind the scenes for every single related entity touched while serializing...
context.Vendors.Where(x => x.VendorId == 1);
context.Vendors.Where(x => x.VendorId == 1);
// ... continue for each and every contract returned in the above list...

如果合同也有一个员工参考......

context.Employees.Where(x => x.EmployeeId == 16);
context.Employees.Where(x => x.EmployeeId == 12);
context.Employees.Where(x => x.EmployeeId == 11);

...对于每个合同和每个相关实体中的每个相关实体/集合,这都会继续。它加起来,很快。通过将分析器连接到服务器并开始读取,您可以看到它有多疯狂。您期望 1 个 SQL,但随后会遇到成百上千的调用。

避免这种情况的最佳方法是简单地不从控制器返回实体,而是仅使用您要显示和使用的详细信息.Select()或 Automapper.ProjectTo<ViewModel>()从 EF 查询中填充它来组合视图模型。这避免了陷入让序列化程序接触延迟加载属性的陷阱,并且还最小化了发送到客户端的有效负载。

因此,如果我想显示供应商的合同列表,我只需要显示合同 ID、合同编号和美元数字:

[Serializable]
public class ContractSummaryViewModel
{
    public int ContractId { get; set; }
    public string ContractNumber { get; set; }
    public decimal Amount { get; set; }
}

var contracts = _dbContext.Contracts
    .Where(c => c.VendorID == vendorId)
    .Select( c => new ContractSummaryViewModel
    {
        ContractId = c.ContractId,
        ContractNumber = c.ContractNumber,
        Amount = c.Amount
    })
    .ToList();

您可以将来自相关实体的详细信息包含到视图模型中,或者为关键详细信息组合相关视图模型,而无需担心使用.Include()或触发延迟加载。这将组成一个 SQL 语句来仅加载您需要的数据,并将其传递回 UI。通过简化有效负载,性能可以显着提高。


推荐阅读