これまでデータアクセス部分の実装はスクラッチで行ってきたのだけど、短期的に納入しなければならない案件があったため、型付きデータセットとアダプターを用いた開発を行うことになった。
その過程の中で自分が感じたところをメモ。
そもそも型付きデータセットとアダプターとは
データストアがSQL Serverであれ、Oracleであれ、MySQLであれ、はたまたACCESSやXMLファイルであっても、UIとDBとのデータのやりとりを行うための中間オブジェクトは必要になってくる。
例えば、社員番号と社員名だけがあるテーブルがある場合、それらをアプリケーションで操作するための中間オブジェクトとそのオブジェクトグラフが必要になる。
しかし、その中間オブジェクトの定義は何か特別な知識やロジックを構築することはほとんどなく、パターン化されているためそこに対して労力をかける時間はもったいない。というわけで、VisualStudioを利用すると既存のテーブルなどからそのスキーマ情報をもとにした中間オブジェクトの定義を自動生成してくれる機能がある。そうやって作られたものが、型付きデータセット。
列名や型などからのクラス定義、さらにはシンプルな形でのCRUDロジック、そして制約などの検証機構までも作ってくれる。
たとえ列名や型などのスキーマ構造に変更がおきても、自動生成機構を再度実行するだけで、特別な時間をかけることもなく中間オブジェクトを再構築してくれるメリットもある。
また、.NET Framework標準のUIコンポーネントはデータバインド機構に対応しているため、型付きデータセットをそのままデータバインドするだけで、UI上で行った変更を特別なコードを書かずにそのまま型付きデータセットを中間オブジェクトとして、データストアに反映することもできる。バインド先のUIがグリッドであれば、変更された行、変更されなかった行、追加された行という、行のステータス管理も自動で行われる。簡易なアプリケーションを開発する上でこんなに素晴らしいことはござらん。
これだけきくと、なんでもできて素早く開発できる銀の弾丸のようなソリューションに見えるけど、残念なことに実際に利用してみると課題があがってきた。そちらをいくつかご紹介。
デザイナが壊れる
これはまいった。似たような役割のテーブルは同じ型付きデータセットの管理ファイルにいれておきたいのだけど、これがGUIベースであったり、1つの定義グループに管理ファイルが4ファイルほどにまたがるため、内容が多くなってくるにつれてデザイナ画面が開きにくくなったり、描画が行われなくなってしまう。自動生成によって数千行一気に構築されることも要因だと思うが、新しく追加した場合や修正した場合に予想以上の時間がかかってしまった。
自動生成される名前空間も自動決定される
クラスとして所属する名前空間はフラットだが、開発の都合上論理的なフォルダにわけて管理したい場合もあると思う。ところが、自動生成の過程でこれらの階層も名前空間の自動生成に関与してしまうため、意図しない構成が生まれてしまう。自動生成のルールにひきづられてしまうのだ。
自動生成はデータアクセスコンポーネントに依存する
当たり前のことだけど、SQL ServerとOracleではデータアクセスコンポーネントは異なる。そのため関数の使い方やパラメータの利用方法は各々のコンポーネントの設計のよって自動生成される。これはいいのだけど、問題はバージョンが異なると生成ルールも大きく変わってしまうのである。
例えばスクラッチで開発していた場合は、バージョンをあげることで利用できなくなったパラメータなどが局所的にコンパイルエラーでわかるため、その部分だけを修正するという履歴管理が可能である。しかし、自動生成によって作り直されると数千行の更新と並び順なども補償されないため、変更管理はほぼ絶望的である。
可変フィルタリング条件に弱い
型付きデータセットとアダプターは基本的にはクエリを疑似的に実行してその結果の型を自動生成されるものである。このときそのクエリ自体も当然関数の内部定義としてコードに含まれる。
しかし、クエリというものは動的に変更したい局面が多々ある。たとえば、社員名と番号を検索する画面があった場合、社員名と番号をそれぞれ必ず入力して検索するのであれば、当然クエリにもWHERE句に社員名と番号のパラメータは与えられる。
しかし、社員名と番号を空白で検索されたときはどうだろうか? もちろん WHERE 社員名 = ISNULL(@社員名, 社員名)などのようにクエリを定義し、パラメータが与えられなければ全行対象とするようなクエリも組めるだろう。だが、これが非常に多くなってくるとクエリの可読性がかなり落ちてしまうほか、BETWEEN句のようなものに無理やり適用しようとすると、結果的にはクエリ効率が落ちてしまいパフォーマンスが低下する。
そもそもパラメータがNULLならば、WHEREの条件をつくらないのが好ましいということになる。
自動生成される1関数の中には自動的に1クエリが静的に内包されるため、可変フィルタリング条件は作れないのだ。
ちなみにこの場合は、自動生成されたアダプタークラスのPartial定義を別建てし、そこに独自の関数を定義することが可能である。というか、今回のプロジェクトではこればかりになってしまった。
型だけ利用することも難しい1
前述の動的なクエリ構築には難があるが、自動生成される型情報は非常に便利である。
ただしこれについても多少問題はある。
データベースはこの列は文字、この列は数字という定義を持っているだけでなく、何文字まで何バイトまで精度は何という情報をもつ。しかし、開発言語では文字型や数値型という定義はあっても、何文字までといった情報までもネイティブで持つ型は存在しない。このインピーダンス・ミスマッチを自動生成は、値の検証というやり方で実現している。
厄介なのが文字列型である。charやvarcharの列に対して自動生成された列はC#ではstringとしてマップされるが、もともとの列がNULL許可であってもそうでなくても、自動生成の初期値はその列オブジェクトから値をとりだそうとしたときにNULLだったら例外を発生させるという生成なのである。
これはデザイナ上のプロパティからNULLでいいか、それとも例外を発生させるか、別の値に変換するか選択できるのだが、なんらかの事情からその型付き情報を再構築したときはクリアされてしまう。
つまり、以前の実装者がそのプロパティを変えたということを知らなければ、再構築したときにオブジェクトの利用箇所で実行時エラーが発生する可能性があることを意味する。前述したように履歴を簡単に終えるのであれば、あとから実装した人の責任と割り切ることもできるが、数千行も生成されて差分が局所的に負えないのである。これは痛い。
ちなみにエラーを発生させるという初期値の場合、オブジェクトの参照箇所では以下のように記述しなければならない。
if (!Is文字列Null())
{
string value = obj.文字列;
}
このような入れ子の値取得箇所が無数に発生するのである。
型だけ利用することも難しい2
今回のプロジェクトではUI層⇔Webサービス層⇔DBという3層構造をとる必要があったのだが、UI層とWebサービス層の間のサービス境界を超えるオブジェクトとしても、型付きデータセットを利用していた。
サービス境界はSOAP通信によって行っていたのだが、そのSOAP定義に型付きデータセットを利用した場合、単純な型情報だけでなく、最大文字数やステータス情報も当然含まれてくる。
これは当たり前といえば当たり前なんだけど、厄介な事実でもある。
なぜなら、中間オブジェクトとはいいつつも限りなくDBの投影に近い存在であるがゆえに、結果的にUI層までもDB層を意識させるためである。
たとえば中間オブジェクトとしてスクラッチで純粋な型情報だけを持つオブジェクトを採用していた場合、データベース側の列定義である列の最大文字数が10→12に変わったとしても、中間オブジェクトの型はstringという定義だけなので影響はない。(※UIコントロールは10→12文字に変える必要はあるかもしれないが)
しかし、型付きデータセットの場合はサービス境界を超えたときの厳密な型情報と値の再構築のためにズレが許されない。DB層における修正がその層だけに完結せず、UI層まで波及するのである。これは非常に痛い。
制約ルールがDBとずれる
自動生成によってデータストアのスキーマ定義とルールを一生懸命取得する仕組みがあるのは前述のとおりだが、これが不十分な箇所がある。それが照合順序がらみの部分である。
たとえば半角数字の1と全角数字の1はデータの本質的には異なる。しかし、データベース側でそれらを同一と扱うか異なるものとして扱うのかは決定権がある。aとA "a "と"a"なども同じである。
先日データベースから結果セットを取得する際に、型付きデータセットへFillする部分でエラーが発生するという現象があった。結果として、Oracle上ではキー列として1と1が異なるものとして共存できていたが、なぜか型付きデータセットがそれを制約違反としてエラーを送出していたのである。ちなみに型付きではなく、DataTableで取得するとこの現象は発生しない。
業務要件として、1と1は別に扱う必要があるケースは稀だと思われる。そう考えれば、DB上の照合順序で同一のもの扱いとしてしまえば、そもそもイレギュラーデータが入るのは防ぐこともできるし、登録の業務検証の部分で正確に行えばこのような事象は防げるだろう。ただし、だからといって、データベースの投影であるはずの中間オブジェクトなのに、その投影ができないのはおかしな話だと思う。
なぞの読み取り専用列ルール
海外のフォーラムでも同様の報告があったのだが、自動生成される列の定義でReadOnlyプロパティがtrueになったりfalseになったりする条件があやふやなのである。
私が発見した限り、結果列の自動生成時にその列がCase文で構築されていると、ReadOnlyとなる。逆に何らかの列を直接返却しているとReadOnlyにはならない。・・・と思いきや、OrderByで結果列に存在しない行が指定されてりうときは、すべてReadOnlyにはならない。
取得してきた型付きのデータテーブルの内容を一部業務的に書き換えたりしたうえで、CSVや画面出力したいケースはふつうにある。その自由度を奪ってなぜReadOnlyと決めつけてしまうのかがよくわからない。
なぞの返却型マッピング
ストアドプロシージャから関数を自動生成した場合に、その戻り値が最初に定義したときと、その後プレビューから再構築したときで型が変わってしまうことがある。。。ストアドの戻り値が数値だとして、はじめはintになるのだが、プレビューして完了するだけでObject型になってしまったり。こればかりは意味不明である。そして非常に厄介である。
バインドも要注意
これは決して型付きデータセットだけの問題ではなく、DataGridViewの兼ね合いもあるのだけど、参考までに。
グリッドに直接バインドするということは、自身でアイテムの追加なども行わなくてよいのでコードもすっきりする。しかし、バインドした以上はデータベースの投影であるため、不完全な行のコミットは許されない。
例えば名前と社員番号の必須列がバインドされているとき、グリッド上でとりあえずこれから10人登録するから、まずは社員番号だけ1から10までうっていくことは許されない。1をうったらその人の名前を入力してでないと2へは移れないのだ。こういった挙動はユーザーの操作性を大きく失ってしまう。
また、あまり設計がよくないが例えば2000件のデータをグリッドにバインドするとしよう。その状態で編集可能な列にフォーカスをいれて移動しようとすると、思い切りフリーズするはずである。
これはなぜかというと、変更されたと判断したバインド元の型付きデータセットが内部的に制約違反していないか2000件の内部レコードを走査しているためである。完全な整合性を保つためには仕方のないことだが、これも著しく操作性を失うことになる。
さらに正確な条件はわからないが、1行もバインドがない状態で新規行の追加をしたときや、それらをESCキーでキャンセルしたとき、また、空白が許されない列ではあるがESCキーで行の編集行為自体をやめざるをえないときなど、特定の操作をしてゆくと、描画されているグリッドの行数と内部のバインド元のテーブルの行数がバインドしているにもかかわらずズレてきてしまって、アプリケーションエラーが発生することがあった。
今回のプロジェクトではこういった理由もあり、結局グリッドと直接バインドすることはやめてしまった。
しかしそれゆえに行の変更管理や削除行の扱い、新規行に対するデータオブジェクトの追加といったコードをスクラッチで書かざるをえなくなってしまい、UI層についてはあまり恩恵を受けられなかったのである。
最後に
散々欠点ばかり書いてきたが、特定の要件に絞られた画面であれば、型付きデータセットは実装も簡素であり非常に開発生産性が高い。それは以下のような画面である。
○グリッドは利用するが、編集はしない。
○編集するとしても件数が少なく、特定の人しか利用しない。あまり使わない。
○DBは直接操作させたくないが、システム管理者などにそのテーブルだけ編集させたい。
○多層型アプリケーションではなく、サービス境界を超えることがない。
○あまりトランザクションを意識しなくてよい
トランザクションについては、決してまったくだめというわけではないのだが、どちらかというと悲観的同時実行制御に近しいような業務オブジェクトの場合は、スクラッチのほうが結局は開発しやすいと思う。
例えば、1行1行が独立しているマスタのようなテーブルは他人と更新がぶつかっても、後勝ち先勝ちのルールを明確にしておくことと、自動生成のUPDATEにはすべての列かTimeStamp列を根拠に同時実行制御を行うオプションがあるので、問題になることはほぼないだろう。
しかし、お金にからんだ伝票のように、ヘッダーと明細の関係がある場合、ある人が明細を追加して別の人が明細を追加したというときに、ヘッダーのレコードの更新フラグによる判定を厳密にいれるか、自分が明細を入れる前と入れたあとの件数と明細すべてのキー値を走査するなどを行わないと、伝票全体の金額がずれてしまうなどお金い絡んだ大問題になってしまう。自動生成の比較基準はあくまでもレコード単位であるため、同じテーブル内のほかの兄弟レコードの件数などは当然加味しないからだ。
繰り返しになるが、参照のみ行う要件の画面などではとても開発生産性が高い。
ということは、ユーザーとの設計工程におけるモック提示などがとても簡単であるということである。
この記事を読んでどうしても悪い印象を持ってしまったとしても、一度はぜひ使ってみて、どういうメリットがあるかということはせっかくだしおさえていただきたいと感じる。
Problem
営業だったら提案書や要件定義書、開発ならシステム構成図、管理だったら社内構成図、企画やユーザーサポートならマニュアルなどなど、基本的にデスクワークを伴う職種であれば何らかの資料などは作成するもの。資料作成するとなると必要になってくるのが絵。イメージ。グラフィック。
カタログや装飾のデザイン会社の社員じゃないから専門的な描画とかは必要ないけど、ちょっとした挿絵や他からひっぱってきた絵の簡単な編集ぐらいはさっとできないとなかなか資料作成もうまくいかないことが多いと思います。たとえばこんなことぐらいはできるととても便利。
○Webからひっぱってきた絵で、必要な部分だけ切り出す(=トリミング)
○他の資料で使えるように背景の部分を透明にする(=透過処理)
○全体の色調を変える。グレースケールにする。
○ロゴや文字が入っている都合の悪い部分を消してしまう自分がいままで作ってきたものをあげると、Web画面で使うボタンとか、データベースの絵とか、ワークフローを中心にした各サービスの関連環状輪とか、色違いのおっさんの絵とか。
グラフィックソフト!なんてきくと世の中で有名なのはPhotoshopとかIllustratorだったりするけれど、専門職じゃないんだから、そんなところにいきなり高いお金をつぎ込んでやるのはちょっと費用対効果に問題点。
Resolution
とはいっても、僕もPhotoshopかって実ははじめようとしたのだけど、ちょっと待てこんなのあるよと教えてもらってから愛用してるのがGIMPというフリーの編集ソフト。
GIMP - The GNU Image Manipulation Program
http://www.gimp.org/

先にあげた3つのこともできるし、簡単なお絵かきもできる。というか簡単どころですまないレベルの機能が搭載されてます。
世の中にグラフィック編集ツールでフリーってのは結構あるんだけど、GIMPはその中でも有名な部類にはいってて、ちょっと大きな本屋にいけばふつうに教本が売ってるのです。ぼくもその教本でかるく2日練習していろいろできるようになった口。グラデーションのかかったきれいなボタンとかも作れるからWebアプリ作成してる人なんかも、ぜんぜん開発に使えると思うよ。
あとフリーってのはその会社の購入物じゃないから、たとえば転職しても使えるし個人でも使えるしいいことづくめ。それにグラフィック編集の基礎知識とか概念(レイヤーとか色の管理とか保存形式とか)は別にPhotoshopとかGIMPも変わらないから、あとからPhotoshop勉強するときも生かせるはず。
きくところによると、某大企業は研修のときにWord、Powerpoint、グラフィック編集も職種に問わず1日ずつやるみたいね。たしかにこれらはそのあと何十日と続く業務の中で暗示的に作業効率を良くするものだから、個人の学習意欲に依存するんじゃなく教育体制敷いたほうがいいもののひとつな気がするよ。
ひさびさにiTunesのSDKを使ってiTunesの操作をC#から使用としたら件名のエラーがでてコンパイルが通りません。なにがあったんだ。
Symptoms
新規のコンソールプロジェクトを立ち上げて、参照設定にiTunesのCOMコンポーネントであるiTunesLibへの参照を追加し、下記のコードを書いてもエラーでビルドが通らない。SDKのC#リファレンスにも以下で通ると書いてあるのに。

Cause
今回からVisualStudio2010(C#4.0)で開発をはじめたんだけど、どうも相互運用機能型の埋め込みというものが関係しているらしい。詳しいことはわからないなー。
Resolution
iTunesLibの参照設定を右クリックして、相互運用機能型の埋め込みっていうプロパティがTrueになってるので、それをFalseに変更すればOK。
具体的な理由はMSDNにそれっぽいことが書いてあったけど、消化しきれなかったよ。ただ、一応ここのプロパティを変えなくても遅延バインディングの仕組みを使い、リフレクションの形で呼び出せばいけるみたい。

C#からiTunesを操作してるイケイケの人たちはご参考までに。