1

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

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

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

实体模型

关联的实体对象如下:

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; }
}

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

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();

现在,虽然这确实有效,但它运行得非常糟糕。查询需要一秒钟才能运行,全部用于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 上显示的此查询的持续时间表明此查询需要很长时间才能运行。查询的有趣之处在于,它根本没有过滤——它返回的是整个树!我不明白为什么它会返回所有内容,因为过滤器myObjectSet.Where(c => c.FormID == 1)非常明确。实际返回的对象仅包含一个条目,这是我所期望的。

我的整个数据访问层都遇到了这个问题,它的性能令人震惊。我不知道为什么生成的查询不包含过滤器 - 也不知道如何告诉它这样做。有人知道答案吗?

4

1 回答 1

3

TL;DR 删除AsEnumerable调用并将其替换为AsQueryable调用,它应该可以解决大部分性能问题(除了实际的数据库执行成本很慢,这是通过在您正在过滤/加入的列上添加索引来解决的)。

解释实际发生的事情......

一旦你打电话AsEnumerable,你现在就在实体框架之外,进入了 LINQ-to-objects 的世界。这意味着它将在枚举时对数据库执行查询。再次调用并不重要AsQueryable,这仅意味着您正在创建针对内存结构的查询。

有效的执行是这样的。

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

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

一旦该查询完成并具体化,它的结果将包含在一个枚举器中。

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

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

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

于 2012-07-30T23:13:21.727 回答