C# 使用实体框架执行简单查询时出现严重的性能问题
我有一个相当通用的CRUDWebApp,它根据几个数据库表的内容动态生成页面。我使用EntityFramework4.0将这些数据从数据库中提取出来,但是我遇到了严重的性能问题。我已经成功地迭代到一个问题中,这个问题包含的内容足够详细,我可以在下面详细介绍 我有一个包含页面表单列表的表(~200)。每个表单都有一个或多个字段(总计约4000个),每个字段可能有一些参数(总计约16000个) 我在下面附上了我的模型的截图: 关联的实体对象如下所示:C# 使用实体框架执行简单查询时出现严重的性能问题,c#,performance,entity-framework,entity-framework-4,C#,Performance,Entity Framework,Entity Framework 4,我有一个相当通用的CRUDWebApp,它根据几个数据库表的内容动态生成页面。我使用EntityFramework4.0将这些数据从数据库中提取出来,但是我遇到了严重的性能问题。我已经成功地迭代到一个问题中,这个问题包含的内容足够详细,我可以在下面详细介绍 我有一个包含页面表单列表的表(~200)。每个表单都有一个或多个字段(总计约4000个),每个字段可能有一些参数(总计约16000个) 我在下面附上了我的模型的截图: 关联的实体对象如下所示: public class Form {
public class Form
{
public int FormID { get; set; }
public string FormName { get; set; }
public IList<FormField> FormFields { get; set; }
}
public class FormField
{
public int FieldID { get; set; }
public string FieldName { get; set; }
public int FormID{ get; set; }
public IList<FormFieldParameter> 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; }
}
sql事件探查器上显示的此查询的持续时间表明,此查询需要花费很长时间才能运行。查询的有趣之处在于,它根本没有过滤功能——它返回的是整个树!我不明白为什么它会返回所有内容,因为过滤器myObjectSet。其中(c=>c.FormID==1)
非常明确。实际返回的对象只包含一个条目,这是我所期望的
我的整个数据访问层都存在这个问题,其性能令人震惊。我不知道为什么生成的查询不包含过滤器,也不知道如何告诉它这样做。有人知道答案吗?TL;DR删除AsEnumerable
调用,并将其替换为AsQueryable
调用,它应该可以解决大多数性能问题(实际数据库执行之外的成本很低,这是通过在筛选/加入的列上添加索引来解决的)。
解释实际发生的事情
只要调用aseneumerable
,您现在就置身于实体框架之外,置身于LINQ to对象的世界中。这意味着在对数据库进行枚举时,它将对数据库执行查询。再次调用AsQueryable
并不重要,这仅仅意味着您正在针对内存中的结构创建查询
有效的执行是这样的
ToList
时,它将执行第一步中的查询(如您所见)。由于返回的数据量和/或丢失的索引可能会提高查询的性能,因此此查询可能很昂贵并且需要一段时间
一旦查询完成并具体化,它的结果将包装在一个枚举器中
现在,对每个对象进行迭代和检查,以查看它是否匹配在步骤3中添加的谓词。如果它确实匹配,那么它将返回给迭代它的人(在本例中是ToList函数)
现在值已经返回,它被添加到使用值创建的列表中
最后,您从
ToList
方法返回一个列表,它完全符合您的要求,但它在内存中而不是数据库中完成了所有这些操作。您需要。可以在CreateObjectSet上查询您阅读了吗?哇,谢谢。将.AsQueryable添加到CreateObjectSet的末尾确实有效。我想既然.Where语句返回了一个IQueryable,就没问题了。我想不是!您知道这种行为的原因吗?LINQ查询代码块正是您正在使用的代码吗?或者您是从方法中检索部分内容还是将其传递到方法中?我刚刚测试了它,无法确认是否需要AsQueryable()
(这会让我很困惑)。使用上面的代码创建一个SQL查询,该查询包含预期的WHERE子句:在第二个左外部联接的正下方,我得到一个WHERE 1=[Extent1].[FormID]
。很抱歉,这是一个用于执行筛选的方法调用,我没有确切说明它在做什么。基本上,它将ObjectQuery转换为IEnumerable,执行过滤器,然后再转换回IQueryable。。。我已经阅读了相关的问题,完全有理由解释为什么它失败了。我将用缺失的信息更新我的问题。非常清楚地解释引擎盖下发生的事情,非常感谢。
EntityConnection myConnection = new EntityConnection("name=myModel");
if(conn.State != ConnectionState.Open) {
conn.Open();
}
ObjectContext context = new ObjectContext("name=myModel");
context.ContextOptions.LazyLoadingEnabled = false;
ObjectQuery<PageForm> myObjectSet = context.CreateObjectSet<PageForm>()
.Include("FormField.FormFieldParameter");
//Edit: I missed this part out, sorry. In hindsight, this was exactly what was
//causing the issue.
IEnumerable<PageForm> myObjectSetEnumerable = myObjectSet.AsEnumerable();
IQueryable<PageForm> myFilteredObjectSet = myObjectSetEnumerable.Where(c => c.FormID == 1)
.AsQueryable();
List<PageForm> myReturnValue = 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