阻止EF包括相关实体

时间:2019-07-16 20:55:42

标签: c# asp.net entity-framework

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

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

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

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

结果是,EF正在为我的Contract加载所有相关的实体,因此这使我的查询速度降低了很多。

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

System.Data.Entity.DynamicProxies.Contract_3EF6BECBB56F2ADDDA6E0050AC82D03A4E993CEDF4FCA49244D3EE4005572C46

Contract上的相关实体也是如此:

System.Data.Entity.DynamicProxies.Vendor_4FB727808BD6E0BF3B25085B40F3F0B9B10EE4BD17D2A4C600214634F494DB66

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

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

我有什么办法吗?

1 个答案:

答案 0 :(得分:1)

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

说我获取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,但随后却受到数百至数千个调用的攻击。<​​/ p>

避免这种情况的最佳方法是不直接从控制器返回实体,而是仅使用要显示的详细信息构成视图模型,并使用.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。通过简化有效负载,性能可以大大提高。