使用Entity Framework执行简单查询时出现严重的性能问题

我有一个相当通用的CRUD webapp,它根据几个数据库表的内容动态生成页面。 我正在使用Entity Framework 4.0将这些数据从数据库中提取出来,但是我遇到了严重的性能问题。 我已经设法迭代到一个足够包含的问题,我可以在下面详述。

我有一个包含页面表单列表(~200)的表。 每个表单都有一个或多个字段 (总共约4000个),每个字段可能有一些参数 (总共约16000个)。

我在下面附上了我的模型的截图:

实体模型

关联的实体对象如下:

public class Form { public int FormID { get; set; } public string FormName { get; set; } public IList FormFields { get; set; } } public class FormField { public int FieldID { get; set; } public string FieldName { get; set; } public int FormID{ get; set; } public IList FormFieldParameters { get; set; } public Form ParentForm { get; set; } } public class FormFieldParameter { public int FieldParamID{ get; set; } public string Value{ get; set; } public int? FieldID { get; set; } public FormField ParentField { get; set; } } 

以下代码提取ID为“1”的Form的所有数据。

 EntityConnection myConnection = new EntityConnection("name=myModel"); if(conn.State != ConnectionState.Open) { conn.Open(); } ObjectContext context = new ObjectContext("name=myModel"); context.ContextOptions.LazyLoadingEnabled = false; ObjectQuery myObjectSet = context.CreateObjectSet() .Include("FormField.FormFieldParameter"); //Edit: I missed this part out, sorry. In hindsight, this was exactly what was //causing the issue. IEnumerable myObjectSetEnumerable = myObjectSet.AsEnumerable(); IQueryable myFilteredObjectSet = myObjectSetEnumerable.Where(c => c.FormID == 1) .AsQueryable(); List myReturnValue = myFilteredObjectSet.toList(); 

现在,虽然这确实有效,但它运行得非常糟糕。 查询需要花费一秒钟才能运行,其全部内容都在myFilteredObjectSet.toList()调用中使用。 我在我的数据库上运行了一个分析器来查看导致延迟的原因,并发现正在生成以下查询:

 SELECT [Project1].[FormID] AS [FormID], [Project1].[FormName] AS [FormName], [Project1].[C2] AS [C1], [Project1].[FormID1] AS [FormID1], [Project1].[FieldID] AS [FieldID], [Project1].[FieldName] AS [FieldName], [Project1].[C1] AS [C2], [Project1].[FieldParamID] AS [FieldParamID], [Project1].[Value] AS [Value], [Project1].[FieldID1] AS [FieldID1] FROM ( SELECT [Extent1].[FormID] AS [FormID], [Extent1].[FormName] AS [FormName], [Join1].[FieldID] AS [FieldID], [Join1].[FieldName] AS [FieldName], [Join1].[FormID] AS [FormID1], [Join1].[FieldParamID] AS [FieldParamID], [Join1].[Value] AS [Value], [Join1].[FieldID1] AS [FieldID1], CASE WHEN ([Join1].[FieldID] IS NULL) THEN CAST(NULL AS int) WHEN ([Join1].[FieldParamID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], CASE WHEN ([Join1].[FieldID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2] FROM [dbo].[PageForm] AS [Extent1] LEFT OUTER JOIN (SELECT [Extent2].[FieldID] AS [FieldID], [Extent2].[FieldName] AS [FieldName], [Extent2].[FormID] AS [FormID], [Extent3].[FieldParamID] AS [FieldParamID], [Extent3].[Value] AS [Value], [Extent3].[FieldID] AS [FieldID1] FROM [dbo].[FormField] AS [Extent2] LEFT OUTER JOIN [dbo].[FormFieldParameter] AS [Extent3] ON [Extent2].[FieldID] = [Extent3].[FieldID] ) AS [Join1] ON [Extent1].[FormID] = [Join1].[FormID] ) AS [Project1] ORDER BY [Project1].[FormID] ASC, [Project1].[C2] ASC, [Project1].[FieldID] ASC, [Project1].[C1] ASC 

sql profiler上显示的此查询的持续时间显示此查询正在运行这么长时间。 关于查询的有趣之处在于它根本没有过滤 – 它返回整个树! 我无法理解它为什么会返回所有内容,因为filtermyObjectSet.Where(c => c.FormID == 1)非常明确。 实际返回的对象只包含一个我想要的条目。

我在整个数据访问层中遇到此问题,其性能令人震惊。 我不知道为什么生成的查询不包含filter – 并且不知道如何告诉它这样做。 有人知道答案吗?

TL; DR删除AsEnumerable调用并将其替换为AsQueryable调用,它应解决大多数性能问题(实际数据库执行成本之外的速度很慢,通过在要过滤/加入的列上添加索引来修复)。

解释实际发生的事情……

一旦调用AsEnumerable您就会出现在Entity Framework之外以及LINQ-to-objects的世界中。 这意味着它将在枚举时针对数据库执行查询。 再次调用AsQueryable并不重要,这仅仅意味着您要在内存结构中创建查询。

有效的执行是这样的。

  1. 创建一个对象查询,包括链接到表单的所有FormFieldProperties
  2. 将当前IQueryable实例转换为可枚举实例。
  3. 为可枚举实例添加谓词,该实例仅返回FormID值为1的项。
  4. 调用ToList,将所有值从源可枚举复制到列表。

现在,直到第4步,查询实际上还没有查询数据库。 当您调用ToList ,它会在第一步中执行查询(如您所见)。 此查询可能很昂贵并且需要一段时间,因为它返回的数据量和/或缺少可能提高其性能的索引。

完成该查询并实现后,它的结果将包含在枚举器中。

现在,迭代并检查每个对象以查看它是否与步骤3中添加的谓词匹配。如果它匹配,则返回给在其上迭代的任何人(在本例中为ToList函数)。

现在已返回该值,它将添加到使用值创建的列表中。

最后,您从ToList方法返回一个列表,它完全符合您的要求,但它在内存中而不是在数据库中完成所有这些操作。