はじめに

基本編にて、ModelLoader.From(Of T)メソッドがある程度理解された方向けのドキュメントです。

事前準備

リレーションのサンプルをゼロから作るのは大変ですので、Kairyuのソースコードをダウンロードし、「samples」フォルダにある「Kairyu.SamplesModel」プロジェクトを使用することとします。
  • ソースコードは「DOWNLOADS」から入手できます。

なお、今回使用するモデルは以下の通り。

PrefectureGroup.vb
Public Class PrefectureGroup
    Public Property PrefectureGroupID As Integer
    Public Property PrefectureGroupName As String
    Public Property Prefectures As ICollection(Of Prefecture)
End Class
Prefecture.vb
Public Class Prefecture
    Public Property PrefectureID As Integer
    Public Property PrefectureGroup As PrefectureGroup
    Public Property PrefectureName As String
End Class
  • 本サンプルに直接関係のないコードは削除しています。

このコードが意味するリレーションは以下の通り。
  • PrefectureGroupは複数(0..N)のPrefectureを持つ。
  • Prefectureは必ず1つのPrefectureGroupを持つ。

また、データは以下のとおり。
PrefectureGroupID PrefectureGroupName
1
2
3
4


PrefectureID PrefectureName PrefectureGroupID
1 北海道 2
省略
47 沖縄県 4
  • マスタデータの全情報は「Kairyu.SamplesModel」プロジェクトの「PrefectureMasterFactory」クラスを参照ください。

Fromメソッドの仕様

今までの記述方法を元に、PrefectureID=1(北海道)のデータを読み込んでみましょう。

Imports Kairyu.Extensions.IO
Imports Kairyu.Load
・・・
Dim m = DB.Connect(Function(r) r.From(Of Prefecture).WhereKeyEquals(1).ToItem)
生成されるSQL
SELECT
T0.PrefectureID AS T0_0, T0.PrefectureName AS T0_1, T0.PrefectureGroupID AS T0_2 
FROM 
Prefectures T0 
WHERE 
T0.PrefectureID = 1
  • ORMのコードを記述するときは、拡張メソッドを使用するために「Kairyu.Load」をインポートしておいてください。

標準ではPrefectureGroupsテーブルへのアクセスが発生しません。
  • ですが、このときの m.PrefectureGroup 初期値(通常はNothing)ではありません。

いままでリレーションについて説明していませんでしたが、リレーションのあるテーブルは少々特殊なマッピングをします。テーブルのデータをもとにどのプロパティにマッピングされたかを図式化します。
Column Value Property
PrefectureID 1 PrefectureID
PrefectureName 北海道 PrefectureName
PrefectureGroupID 2 PrefectureGropup.PrefectureGropupID


列「PrefectureGroupID」が、PrefectureクラスのPrefectureGroupプロパティにあるPrefectureGropupIDプロパティにマッピングされていることに注目してください。
この仕様により、先程のコードの 変数m のPrefectureGroupプロパティはNothingではありません。
「m.PrefectureGropup.PrefectureGropupID」には「2」の値が格納されています。

また、PrefectureGropupプロパティはPrefectureGropupIDプロパティにしかマッピングされてないことにも注目してください。
この仕様により、先程のコードの m.PrefectureGropup.PrefectureGropupName はString.Empty になっています。

つまり、標準では自層(Prefecture)のマッピングは完全ですが、上層(PrefectureGroup)は不完全ということになります。
このような仕様になったのは以下の理由によります。
  • どこまでのリレーション情報を取ってきたらいいか、フレームワークでは判断が付かない。リレーション情報を根こそぎ持ってくることも可能ですが、オーバーワークの可能性があるので、自層しか読み込まない。
  • PrefectureクラスにPrefectureGroupIDプロパティを作ってマッピングする方法も考えましたが、PrefectureGroup.PrefectureGroupIDプロパティとの整合性を担保するのが困難。

上層を読み込む

上層を読み込むには、以下の3種類の方法があります。

一括で読み込む(Batch)

上層は「必ず0..1個存在する関係」であるため、どれだけ上層を遡っても読み込み元のクラスにとっては1レコード1クラスであることが担保されます。
よって、1クエリで上層読み込みを表現することが可能です。この考えをコードで表すと以下のようになります。

VB.NET
Imports Kairyu.Extensions.IO
Imports Kairyu.Load
・・・
Dim m = DB.Connect(Function(r) r.Cascade(1).From(Of Prefecture).WhereKeyEquals(1).ToItem)

生成されるSQL
SELECT 
T0.PrefectureID AS T0_0, T0.PrefectureName AS T0_1, T0.PrefectureGroupID AS T0_2, T0_T1.PrefectureGroupName AS T1_1 
FROM 
Prefectures T0 
INNER JOIN 
PrefectureGroups T0_T1 
ON 
T0.PrefectureGroupID = T0_T1.PrefectureGroupID 
WHERE 
T0.PrefectureID = 1

ポイント

  • From(Of T)メソッドを実行する前にCascadeメソッドにて、「1層上層へカスケードしなさい」と指示します。
  • Cascadeメソッドを指定しない場合は、「0:自層のみ」となっています。
  • 無制限にカスケードしたい場合は、Cascasdeメソッドの代わりにFullCascadeメソッドを指定してください。
    • 自己参照テーブルの場合、無制限カスケードは有効になりません。

1つずつ読み込む(Iterate) [ver0.2.1より]

カスケード上限を指定する方法は非常に簡単に記述できますが、上層データは重複している可能性があり、場合によっては非効率となる可能性があります。その場合は、1回自層のマッピングをし終わってから、上層のマッピングを行うことで、親の重複を除去できます。この考えをコードで表すと以下のようになります。

VB.NET
Imports Kairyu.Extensions.IO
Imports Kairyu.Load
・・・
Dim m As Employee = Nothing
DB.Connect(Sub(r) m = r.Iterate.Cascascade(1).From(Of Employee).WhereKeyEquals(5).ToItem)
生成されるSQL
SELECT 
T0.PrefectureID AS T0_0, T0.PrefectureName AS T0_1, T0.PrefectureGroupID AS T0_2
FROM 
Prefectures T0 
WHERE T0.PrefectureID = 1
/
SELECT 
T0.PrefectureGroupID AS T0_0, T0.PrefectureGroupName AS T0_1 
FROM 
PrefectureGroups T0 
WHERE 
T0.PrefectureGroupID = 2

ポイント

  • Fromメソッドを呼ぶ前に、Iterateメソッドを使用してください。
  • SQL文が2つ生成されている点もご注意ください。つまり、DBへ2回アクセスが発生しています。

1つずつ読み込む(手動)

Cascadseメソッドを使用すると、自動的に上層カスケードがされますが、過剰にデータを読み込んでしまうことがあります。
本フレームワークとしては、”ある程度過剰なのはO/RMだから仕方ない"というスタンスですが、一応手動で指定することも可能です。その代り、あまり使い勝手はよくありません。
Imports Kairyu.Extensions.IO
Imports Kairyu.Load
・・・
Dim m = DB.Connect(Function(r) r.From(Of Prefecture).WhereKeyEquals(1).Execute.LoadParent(Of PrefectureGroup).ToItem)
生成されるSQL
SELECT 
T0.PrefectureID AS T0_0, T0.PrefectureName AS T0_1, T0.PrefectureGroupID AS T0_2
FROM 
Prefectures T0 
WHERE T0.PrefectureID = 1
/
SELECT 
T0.PrefectureGroupID AS T0_0, T0.PrefectureGroupName AS T0_1 
FROM 
PrefectureGroups T0 
WHERE 
T0.PrefectureGroupID = 2

ポイント

  • 読み込んだモデルを元に追加でORMを実行するには、ToList(ToItem)メソッドではなく、Executeメソッドを使用してください。
  • Executeメソッドを使用すると一度DBへアクセスが発生し、ORMが起きます。
  • Executeメソッド後の追加処理として、LoadParent(Of T)メソッドを指定。親プロパティタイプがTのものを追加読み込みします。
  • LoadParent(Of T)メソッドの後は、ToList(ToItem)メソッドを使用して値を取得してください。

まとめ

Batch

Good
  • 記述量が少ない。
  • DBMSとの通信回数は1回だけ。
Bad
  • 階層単位より細かい読み込み指定はできない。
  • 複数行読み込みの場合、重複したデータも転送してしまう。
  • カスケード中に外部結合が入ると、以降の上限がすべて外部結合になってしまう。
Remarks
  • 重複データが発生しにくい単一行読み込みに適している。
  • 標準はこのロジックが採用されています。
  • O/RMの仕様というより構造の問題ですが、外部結合があるとパフォーマンスが悪くなる可能性があります。極力外部結合ではなく、内部結合になるように設計することをお勧めします。

Iterate

Good
  • 記述量が少ない。
  • 重複したデータの転送がほぼない。
  • クエリが単純画一化されるため、DBMS側でのSQL解析コストが下がる。
  • リレーションをしないSQL文のため、外部結合によるパフォーマンス悪化の影響を受けない。
Bad
  • 階層単位より細かい読み込み指定はできない。
  • DBMSとの通信回数が多い。
Remarks
  • 重複データが発生しにくい単一行読み込みの場合にはあまり適さない。
  • DBMSとの通信回数が多いので、DBMSとの通信環境が高速であることが望ましい。例えばクラサバのように、インターネット回線のような遅い通信環境を使用してDBMSと通信するとパフォーマンスが悪化する可能性あり。
  • 外部結合によるパフォーマンス悪化の影響を受けませんが、極力内部結合になるような設計をお勧めします。

手書き

Good
  • 重複したデータの転送を減らせる。
  • 任意の上層をマッピング指定できる。
Bad
  • 記述量が多く、複雑。
  • DBMSとの通信回数が多い。
  • 任意の上層といっても、連続して記述できるのは1階層上位まで。
Remarks
  • よほどのことがない限り、選択する必要はありません。

カスケード上限の補足(外部結合)

今回の例をあげませんでしたが、「カスケード上限」にてカスケードされるテーブルは「内部結合できるテーブル」に限られます。これを外部結合したテーブルを含める場合は以下のように、IncludeOuterメソッド を指定します。

VB.NET
Imports Kairyu.Extensions.IO
Imports Kairyu.Load
・・・
Dim m = DB.Connect(Function(r) r.FullCascade.IncludeOuter.From(Of Prefecture).WhereKeyEquals(1).ToItem)

Last edited Mar 12, 2013 at 4:04 PM by mk3008, version 2

Comments

No comments yet.