2013年5月23日木曜日

RDBMS利用時のアーキテクチャの選択方針

全エントリのように、各アーキテクチャには一長一短あるわけですが、それでもどれかを選ばないと始まらないので、「僕だったら」こんな方針でアーキテクチャを選ぶ。というところを書きたいと思います。

バッチ系の処理


バッチ系の処理の場合、SELECTで条件に合致したレコードすべてに対して何らかの処理を行う。という形になると思います。この場合は生ADO.net(DbConnection/DbCommand/DbDataReader)が相性が良いと思います。

こいつは古き良きスタイルなので、レコードを一行一行読んで処理して、という形に自然になること。また、DBへの接続と切断をコントロールしやすいのもメリットです。対象のRDBMSに応じて、DbXxx抽象クラスの派生クラスを使うことで、それぞれのRDBMSの特性を生かした実装ができるのもいいですね。

(型あり|型無し)データセットだと、一旦まとめて読みこむ形になるので、バッチ系処理ではかえって制御が難しいように思います。

EF(Entity Framework)もバッチ処理で使いにくいわけではなさそうですが、たとえばSQLServerなんかだとロックエスカレーションによるデッドロックを回避するために、SELECT時にロックヒントを付けたりしますが、こんな感じのRDBMSごとの配慮がいちいち面倒なイメージがあります。実際にバッチ系処理で使ったことはないですが。

Webアプリケーション


Webアプリの場合はプレゼンテーション側のアーキテクチャを何にするかによると思います。僕の場合は好みの問題で、全力でASP.net MVCを推します。この場合はEFを選択するのがたぶん楽です。

どうしてもASP.net (Webフォーム)から逃げられなかった場合は、型無しデータセットかな。

その他 (2層C/S、3層C/S、Webサービス、etc...)


そのほかはケースバイケースになりますが、メリットデメリットを加味しながら、基本的には好みでEFか、型無しデータセットか、生ADO.netのいずれかを選ぶかな。この3つ以外は選択肢から除外。

ただし…


EFを使う場合は、モデルファーストかコードファーストで開発を始めるのがよいと思います。コードファーストはいろいろ批判的な意見も見かけますが、僕は結構好きです。
それと、インデクッスやトリガ、ストアドプロシージャなんかはどう使うか、何処で使うか、どのタイミングで実際に適用するか。ここら辺は十分な配慮が必要でしょう。あと、ビューは使いづらいです。多分。

型無しデータセットを使う場合は、コードのメンテナンシビリティを保つために、以下の2つを守ることがおススメです。
これを守ることで、前のエントリで列挙した型無しデータセットのデメリットがだいぶ減り、
  • DataTableの各レコードに対して、Enumerable拡張メソッドが使いやすくなり、コードが比較的きれいになる。
  • フィールドからのデータ取得時のキャストが不要になる。また、Nullableを使ってDBNullをNULLにマップできる。
と、形無しデータセットでも、少しはイマドキなコードが書けるようになります。例をあげてみましょう。たとえば、取得済みのDataTable「t」から、「Kana」順に「Name」と「Birthday」を取得してイロイロ。しかも「Birthday」はNullable。ならこんなコードになります。

DataTable t = GetTable();

foreach (var row in t.AsEnumerable().OrderBy(r => r.Field<string>("Kana")))
{
  var name = row.Field<string>("Name");
  var birthday = row.Field<DateTime?>("Birthday");
  // name,birthdayを使っていろいろ処理
}
このコードは、当然Selectメソッドで匿名クラスを作ったりしても問題なく行けます。個人的は「これなら使ってもいっか。」という気になります。

そして、生ADO.netを使う場合は、多段usingネストを減らす工夫があるとよいでしょう。

とりあえずそこは、ちょっと長くなったので別エントリで書くことにします。

2013年5月21日火曜日

悩ましいRDBMSの利用時のアーキテクチャの選択

唐突ですが、C#(というか.netのアプリケーション)で、RDBMSを利用するのは、.netの登場から10年以上たった今でもどうするのが正解か?いまだに難しい問題だなぁと思っています。

探してみるとRDBMSと.netの間に入る、よさそうなミドルウェアもなくはないんだけど、チームでの開発だとライセンスのコストや習熟にかかるコストが読みづらく、正直選択がためらわれる。

で、.netの標準の仕組みでデータベースアクセスをしようとすると、今なら以下のいずれかを選択することになりますね(新しい順)。
  • Entity Framework
  • LINQ to SQL
  • 型付きデータセット/形無しデータセット
  • 生ADO.net (DbConnection+DbCommand+DbDataReader)

個人的な感想を言うと、どれも一長一短なのです。
  • Entity Framework
    • 物理層のRDBMSを抽象化した、論理層に対する操作を行う。論理層への操作(LINQを使った処理)が物理層(SELECT等のSQLコマンド)にマッピングされる動作はすばらしいと思う。
    • が、特定のフィールドのみ更新したい場合ないど、UPDATEとの相性がいまいち。
    • それと、物理層への操作は基本的に排する方針のようで、インデックスやらトリガやらビューやらはどう組み込んでいいものやら。要は、RDBMSでお気楽にできる処理が、簡単じゃなかったりすることがしばしば起こる。
    • インデックスとかを取り込みにくいので、Webアプリのように徐々にレコード数が増えていくものであれば、DBのスキーマが定まった後に、必要に応じてインデックスを追加していくのはイケルかと思う。でも、最初からデータ移行で数十万レコードを考慮しないとならんようなケースでは、インデックスやトリガやビュー等を最初から考慮した設計を行いたいんだけど、…なんかすごくやりづらい。
    • そもそも、習熟した人がほとんどいない。
  • LINQ to SQL
    • SQLServer以外が選択肢にならない。
    • 今後のメンテナンス/機能追加が期待できない。
  • 型付きデータセット
    • きらい。
    • …(それ以外の理由が必要?)
    • LINQを意識した作りになっていない。
    • 自動で付加されるクラス名に、すごい名前付けてくれちゃったり。おかげでよほどベテランさんが意識してコードを書かないと読みにくいのなんの。
    • Visual Studioのデザイナ(UI)の出来がイマイチ。よくわからない挙動にイラッとすることも度々。イヤになるほど遅いし。ちょっと複雑なSELECT文だとどうにもエラーになっちゃったり。
    • 特定のフィールドのみを出力するSELECTや、複数テーブルをJOINしたSELECTとかは別のクラスにするの?一つのクラスでSELECTごとに埋まる項目が変わるの?クラスを呼び出す側はメソッドによってデータが入ってたり無かったりするのかな?どうもこう、どう注意して作っても統一性が取れなくて。
  • 形無しデータセット
    • 当然、LINQを意識した作りになっていない。
    • フィールド名によるインデクサを多用せざるを得ないため、文字列リテラルが大発生。文字列リテラルに間違いがあると実行時のエラーとなってしまうのでバグの元が山盛りある状態に。
    • さらにインデクサで取得してもすべて形無し(object)になってしまうので、キャストやらコンバータやら、本来処理したいもの以外のコードでソースが埋め尽くされる。
    • そこに持ってきて、DBNullがオブジェクトのNULLにマップしてくれないので、NULLチェックによる処理もソースを汚し倒してくれる。
    • これはもう誰が書いてもそうなる。
  • 生ADO.net (DbConnection+DbCommand+DbDataReader)
    • イイ言い方をすれば「由緒正しい」。それってつまり「古臭い」。
    • それぞれ構築と廃棄を意識して作る必要がある。usingによるネストが大発生。using使わなかったりするとそれはそれで悲しいコードになりがち。

と、まぁ、悩むことこの上ない。そんなこんなで僕ならどうするか。次のエントリで書いてみたいと思います。(解決にはなりません。あんまり期待しないでほしいっす。)

いくつかのエントリのタイトルを変えました。

ブログの開設から2ヶ月半といったところ。イイ感じに更新ペースが落ちてってます(ぉぃ)。

ちょっと前に書いたエントリで、タイトルから内容が分かりづらいものがいくつかあったので、わかりやすいタイトルがつけれるものは直しました。

2013年5月9日木曜日

『荒川弘×田中芳樹「アルスラーン戦記」別マガで連載決定』

「荒川弘×田中芳樹「アルスラーン戦記」別マガで連載決定」だって!

別マガですか。講談社ですか。ほへー。「銀の匙」と「百姓貴族」と3つ掛け持ちってこと?牛先生仕事しすぎ。…あれ?「銀の匙」はまだ終わんないよね…?大丈夫だよね??

まぁ、ハガレンの時も「獣神演武」をパラで書いてましたからね。そーいうのに慣れてるのかな。それにしてもすごいなぁ。

しかし、別マガを連載で追いかけるのは無理だな。単行本化を気長に待つことにします。


2013年5月8日水曜日

ファイルが既に存在する場合に、ナンバリングして存在しないファイル名を取得する処理をLINQで。

タイトルにある、ちょっと無理めなお題を考えてみた。

すなわち、Windowsでよくある処理で、たとえば「U:\Test\Test.txt」が存在しない場合はそのままで、存在する場合は「U:\Test\Test (2).txt」を、それも存在する場合は(3)、(4)、…という風にナンバリングし、存在しないファイル名を返す、そんなメソッドを考えてみる。

LINQは意識せず、深く考えずに書いてみたらこうなった。

static string GetNumberedUniqueFilename(string filename)
{
  if (!File.Exists(filename))
    return filename;

  string d = Path.GetDirectoryName(filename),
    fn = Path.GetFileNameWithoutExtension(filename),
    ext = Path.GetExtension(filename);

  for (int i = 2; ; ++i)
  {
    var f = Path.Combine(d, string.Format("{0} ({1}){2}", fn, i, ext));
    if (!File.Exists(f))
      return f;
  }
}


これをLINQ使って書きなおしてみると…。難しいな…。こうか?

static string GetNumberedUniqueFilename(string filename)
{
  if (!File.Exists(filename))
    return filename;

  string d = Path.GetDirectoryName(filename),
    fn = Path.GetFileNameWithoutExtension(filename),
    ext = Path.GetExtension(filename);

  return Enumerable.Range(2, int.MaxValue - 2)
    .Select(i => Path.Combine(d, string.Format("{0} ({1}){2}", fn, i, ext)))
    .SkipWhile(f => File.Exists(f))
    .First();
}


…これはびみょーだなぁ…。

勘違い

昨日は第2火曜日じゃなかった。イブニング発売日だと思っていくつかKIOSK見て回って。売り切れかと落胆していた。7日は一週目だね。

2013年5月3日金曜日

Enumerable.Zipメソッド

2つのコレクションを、同時に列挙したいケースというのはたまにありますよね。たとえば、2つの異なるデータソース上のデータが、何らかのキーでJoinできるわけではなく、単純に格納順序で順番に取り出してなんかしらの処理をして出力したい。とか。

まぁ、こんなケースはたいていデザイン的に問題があったりするんですけど、とはいえリファクタリングするにはなんだか大掛かりになりすぎちゃって、「そこまでしたくないなぁ。」なとき。

こうなると、foreachで2つのコレクションから順次取り出すことはできないので、for文+インデクサを使ったりするわけですが、こんな感じのコードになりますね。

int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };

for (int i = 0; i < numbers.Length; ++i)
{
  Console.WriteLine("{0}:{1}", numbers[i], names[i]);
}

ところが、今となっては単純な列挙にforとかiとか[]とか、こういうのを見るとなんだか可読性が悪い(…様な)気がして、foreachで置き変えたくなっちゃう。こんなときに使えるのが「Enumerable.Zip」メソッドです。2つのコレクションを、まとめて一つにしちゃってくれる、「.net4」で追加されたメソッドです。たとえば、Tupleと組み合わせることで、こんなコードに置き換えできる。

int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };

foreach (var x in numbers.Zip(names, Tuple.Create))
{
  Console.WriteLine("{0}:{1}", x.Item1, x.Item2);
}

やっぱりこっちのほうがすっきり見えるわけです。完全にLINQに毒されてますな。自覚あります。

Item1とかItem2とか、このコードくらいスコープが短ければ気にならないけど、もう少し複雑な処理で、名前にも気を使いたいような場合なら、Tupleの代わりに匿名クラスを使うことで、こうも書けますね。

int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };

foreach (var x in numbers.Zip(names, (number, name) => new { number, name }))
{
  Console.WriteLine("{0}:{1}", x.number, x.name);
}

どちらでも、ケースバイケースで。あるいはお好みで。