いくつかのデータベース テーブルの内容に従ってページを動的に生成する、かなり一般的な CRUD Web アプリケーションを用意しました。Entity Framework 4.0 を使用してこのデータを DB から引き出していますが、深刻なパフォーマンスの問題が発生しています。以下で詳しく説明できるほど十分に含まれている問題を繰り返し処理することができました。
ページフォーム(~200)のリストを含むテーブルがあります。各フォームには 1 つ以上のフィールド(合計で最大 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();
さて、これは機能しますが、非常にうまく機能しません。クエリの実行には 1 秒以上かかり、そのすべてが呼び出しに費やされ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 プロファイラーに表示されるこのクエリの実行時間は、このクエリの実行に非常に時間がかかっていることを示しています。クエリの興味深い点は、フィルター処理がまったく行われていないことです。つまり、ツリー全体が返されます。myObjectSet.Where(c => c.FormID == 1)
フィルターがかなり明示的であるため、なぜすべてを返すのか理解できません。実際に返されたオブジェクトには、1 つのエントリしか含まれていません。
データ アクセス レイヤー全体でこの問題が発生しており、そのパフォーマンスは恐ろしいものです。生成されたクエリにフィルターが含まれていない理由がわかりません。また、そうするように指示する方法もわかりません。誰も答えを知っていますか?