簡易ORMフレームワークを作成してみる(14)
検索処理の最適化
先程の例では、元の検索の検索結果の件数が多くなるとSQL文が長くなってしまう問題があります。このため、検索条件をいろいろ最適化していく必要があります。たとえば、以下のような元の条件を利用した相関サブクエリを利用する方法や場合によっては全てのレコードを読み出す方法などがあります。
Select [TerritoryID],[TerritoryDescription],[RegionID] From [Territories] Where [TerritoryID] = '01581' Select [RegionID],[RegionDescription] From [Region] Where EXISTS ( SELECT * From [Territories] Where [Region].[RegionID]=[Territories].[RegionID] AND ([TerritoryID] = '01581'))
今回のORMでは、このような検索に対して検索ポリシーを指定できる仕組みを用意してデータアクセスの最適化を行えるようにします。
現時点で用意したポリシーは以下のようなものです。
- 関連テーブルを検索条件の組み立て方式
- 元テーブルの参照キーを利用したID指定(IdMapping)
- 元テーブルの検索条件を利用した相関サブクエリ(NestQuery)
- 上記のいずれかを自動的に選択させる方法(Optimize)
- 検索対象の関連テーブル条件
- 元テーブルの子のテーブルのみ(ChildRelation)
- 元テーブルの親のテーブルのみ(ParentRelation)
- 元テーブルの親と子のテーブル(Relation)
- 元テーブルのみ関連テーブルは検索しない(RootOnly)
- 検索するデータ範囲
- 検索条件で指定した範囲(QueryData)
- 全てのテーブルデータ(AllData)
いろいろアイデアがあるので、バリエーションも増えるかもしれませんが、基本的に検索ポリシーを利用して検索処理の最適化を行うという考え方になります。キャッシュ指定ポリシーの追加などは実用的かもしれません。
検索処理の実装コードの抜粋
public virtual void Fill(DataSet target, QueryObject query) { Fill(target, query, FillPolicy.None); } public virtual void Fill(DataSet target, QueryObject query, FillPolicy policy) { _fillAllTable = new List(); if *1 return; _fillAllTable.Add(schemaTable); query = new QueryObject(); } DataTable workTable = SelectAction(schemaTable, targetTable, query, policy); if *2 != null) { row.Delete(); } } workTable.AcceptChanges(); } }
*1:policy & FillPolicy.MappingMask) == FillPolicy.None) policy |= FillPolicy.Optimize; if ((policy & FillPolicy.DirectMask) == FillPolicy.None) policy |= FillPolicy.Relation; if ((policy & FillPolicy.AccessMask) == FillPolicy.None) policy |= FillPolicy.QueryData; FillRecursive( RootTable, target.Tables[RootTable.TableName], policy, null, query == null ? new QueryObject(): query, null); } protected virtual void FillRecursive(DataTable schemaTable, DataTable targetTable, FillPolicy basePolicy, DataRelation relationPath, QueryObject parentQuery, DataTable sourceTable) { TableSelectingEventArg arg = OnTableSelecting( schemaTable, targetTable, basePolicy, relationPath, parentQuery, sourceTable); QueryObject query = arg.Query; FillPolicy policy = arg.Policy; if (query == null) return; if ((policy & FillPolicy.DirectMask) == FillPolicy.Abort) return; if ((policy & FillPolicy.AccessMask) == FillPolicy.AllData) { if (_fillAllTable.Contains(schemaTable
*2:policy & FillPolicy.DirectMask) == FillPolicy.RootOnly) return; if ((policy & FillPolicy.ChildRelation) == FillPolicy.ChildRelation) { foreach (DataRelation relation in schemaTable.ChildRelations) { if (relation == relationPath) continue; FillRecursive( relation.ChildTable, targetTable.DataSet.Tables[relation.ChildTable.TableName], basePolicy, relation, query, workTable); } } if ((policy & FillPolicy.ParentRelation) == FillPolicy.ParentRelation) { foreach (DataRelation relation in schemaTable.ParentRelations) { if (relation == relationPath) continue; FillRecursive( relation.ParentTable, targetTable.DataSet.Tables[relation.ParentTable.TableName], basePolicy, relation, query, workTable); } } } private DataTable SelectAction(DataTable schemaTable, DataTable targetTable, QueryObject query, FillPolicy policy) { DataSet workDs = targetTable.DataSet.Clone(); DataTable workTable = workDs.Tables[targetTable.TableName]; DbCommand cmd = CommandBuilder.GetSelectCommand(schemaTable, query); CommandHelper.Fill(cmd, workTable, query.StartRecord, query.MaxRecords); NormalizeWorkTable(targetTable, workTable); targetTable.Merge(workTable); return workTable; } private void NormalizeWorkTable(DataTable targetTable, DataTable workTable) { if (targetTable.PrimaryKey.Length > 0) { foreach (DataRow row in workTable.Rows) { ArrayList arr = new ArrayList(); foreach (DataColumn pks in targetTable.PrimaryKey) { arr.Add(row[pks.ColumnName]); } if (targetTable.Rows.Find(arr.ToArray(