ページ

2011年4月3日日曜日

◆LinqとDataGridViewは仲が悪い?

最近MSのDBアクセス技術を勉強し始めている。
まぁ、勉強と言っても仕事で使うわけではないのであくまでも俯瞰するだけ。

ということで以下の書籍をもとにサラッと弄ってみようと軽い気持ちで始めたがいきなり躓く。

躓いたと言ってもこの書籍の中身ではなく(この書籍自体は通称赤間本なので非常に丁寧な説明で分かりやすくお薦めである)、サンプル結果を表示しようとLinqの結果をDataGridViewにバインドした場合である。

一般的にLINQの解説記事なり解説本はLINQの文法の説明になりがちで、LINQを使ってどう業務アプリケーションを組み立てるかという所には触れられていない。
なのでLINQでデータを取り出してコンソール出力して終わりというパターンで終わっている。
サンプルを試していくに当たり、都度コンソール出力するのは面倒だし結果も確認しづらいのでDataGridViewにバインドして確認することとした。

バインド自体は通常の手順で簡単にできるのだが、実際サンプルに載っていたNorthWindの表示で不可解なエラーに悩まされる。
サンプル自体は以下のようになんの変哲もない基本的なものである。

  1. namespace WindowsFormsApplication1  
  2. {  
  3.     public partial class Form1 : Form  
  4.     {  
  5.         public Form1()  
  6.         {  
  7.             InitializeComponent();  
  8.         }  
  9.   
  10.         private void button1_Click(object sender, EventArgs e)  
  11.         {  
  12.             using (NorthWindDataContext nwnd = new NorthWindDataContext())  
  13.             {  
  14.                 nwnd.Log = Console.Out;  
  15.                 var ordersdata = nwnd.Order.Where(o => o.OrderDate >= new DateTime(1996, 7, 1) &&  
  16.                     o.OrderDate <= new DateTime(1996, 8, 1))  
  17.                     ;  
  18.   
  19.   
  20.                 BindingSource src = new BindingSource();  
  21.                 src.DataSource = ordersdata;  
  22.                 dataGridView1.DataSource = src;  
  23.             }  
  24.         }  
  25.     }  
  26. }  

20110403103255


実行すると結果もちゃんと表示されて一見なんの問題もなさそうだ。
しかし、なぜかこの画面を右にスクロールしていくと、
image



こんな例外が発生する。
発生箇所はLINQのデザイナー、
20110403104604 
の陰で自動生成されているコードビハインドソースの中だ。
「破棄されたオブジェクトにアクセス出来ません」?


なんのこっちゃ。
これだから、最近のMSの抽象度の高いお気軽プログラミングはみんなに嫌われてしまう。
ブラックボックスは、それがうまく動いている限りにおいては「魔法の杖」だが、ひとたびトラブルが発生するとアンタッチャブルな「悪魔の化身」になる。
そこで一度ブラックボックスで躓いた人はその後、その手に技術にトラウマができて忌み嫌うようになってしまう。
MS系の技術が嫌われ、みんなオープン系に流れてしまうのは、その中途半端なブラックボックスに原因があるのではなかろうか。
ブラックボックスは完璧であってこそその存在価値があるのだ。
私の周りでもVisualStudioで開発しているチームでさえ、データセットなどを使わず自前でエンティティ(DTO)をつくる傾向にある。


しかし、私的にはMSの目指している方向は正しいと思っている。
プログラミングなんてお気軽に越したことはない。あくまでも道具なのだから。
問題は品質なのだ。

20年ほど前、私の部署にOS製造部隊から異動してきた人がいた。(私の部署は一般の業務アプリケーション開発部署でCOBOLを使っていた)
その人は、COBOLアプリケーションをデバッグするのにいつもアセンブリーリストを追いかけていた。
そして普通のCOBOLプログラマーが見たら3分で気づきそうなバグを1週間もかけてデバッグしていた。
見かねた私は、「COBOLソースを見たほうが早いですよ」と声をかけたのだが、その人は「いや、COBOLのコンパイラーは当てにならんから」とにこやかに言い放った。
私は内心「当てにならないのは、コンパイラーより、そのCOBOLプログラムを書いたあなたの方ですから」と思ったものだ。


まぁこれは極端な例だが最近のMSさん、よく言えば柔軟で守備範囲が広いのだが悪く言うと色んな機能に手を出してそれぞれの技術の品質がおろそかなような気がする。RAD中心かと思えばASP.NET MVCを出してみたりPOCOをサポートしてみたり。
私はまだ使う気がないが、MSが強力に推進しているAzureも推して知るべしといった気がする。
昔に比べればずいぶん高機能で便利なはずなのに結果的に、それでシステムを作ると昔より時間がかかってしまう。
そんな技術ははっきり言って我々には迷惑この上ない。(技術系オタクには面白い世界なのだろうが)
私が最近期待しているLightSwitchにはぜひ完成度の高いブラックボックスとして登場してほしい。


だいぶ脇道にそれたが、本題に戻ってこのエラーを調べてみた。
といっても、そもそもこれからLINQを勉強しようというのだから本質的なところは判るはずもない。あくまでも現状認識と想像である。
ここで自分がアクセスしている(つもり)なのはOrdersテーブルである。
なのに、エラー箇所を見るとどうもPublisherテーブルにアクセスしに行っている箇所の様に見える。
これはどうしたことだろう。
LINQの売りの機能として、リンクが貼られたテーブルの項目をあたかも自分のオブジェクトのプロパティを参照する様にたぐれるというものがある。
実際に、上記でLinqの結果を格納しているOrderdata変数をみて見るとPublisherをプロパティで保持しているのが判る。
image


今回の例で言うと、この結果をそのままDataGridViewのデータソースに指定しているので意図しないPublisherまでデータ取得がされているのだろう。
ちなみに、発行されたSQLを見ると、自分で意図してPublisherの値をたぐった時はJoinが発行されているのだが、今回のようなケースでは単純に1件、1件取得するSQLが発行されているっぽい。


こういう時の為に、リレーションをたぐったプロパティを除いたオリジナル構造のクラスなんかも定義してくれるとか、DataGridViewでEntitySet型はバイパスしてくれるとか、なにか対応があっても良いような気もする。


何かしら対応策があるのかもしれないがなにぶんLINGを使い始めたばかりなのでこれ以上は後回し。
なんにしてもリレーションをたぐって追加されているプロパティが悪さをしているのは確かなので、それを除いてGridViewに表示してあげる。


  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Data;  
  5. using System.Drawing;  
  6. using System.Linq;  
  7. using System.Text;  
  8. using System.Windows.Forms;  
  9.   
  10. namespace WindowsFormsApplication1  
  11. {  
  12.     public partial class Form1 : Form  
  13.     {  
  14.         public Form1()  
  15.         {  
  16.             InitializeComponent();  
  17.         }  
  18.   
  19.         private void button1_Click(object sender, EventArgs e)  
  20.         {  
  21.             using (NorthWindDataContext nwnd = new NorthWindDataContext())  
  22.             {  
  23.                 nwnd.Log = Console.Out;  
  24.                 var ordersdata = nwnd.Order.Where(o => o.OrderDate >= new DateTime(1996, 7, 1) &&  
  25.                     o.OrderDate <= new DateTime(1996, 8, 1))  
  26.                     .Select(o => new  
  27.                     {  
  28.                         o.OrderID,  
  29.                         o.CustomerID,  
  30.                         o.EmployeeID,  
  31.                         o.OrderDate,  
  32.                         o.RequiredDate,  
  33.                         o.ShippedDate,  
  34.                         o.ShipVia,  
  35.                         o.Freight,  
  36.                         o.ShipName,  
  37.                         o.ShipAddress,  
  38.                         o.ShipCity,  
  39.                         o.ShipRegion,  
  40.                         o.ShipPostalCode,  
  41.                         o.ShipCountry  
  42.                     });  
  43.   
  44.                 BindingSource src = new BindingSource();  
  45.                 src.DataSource = ordersdata;  
  46.                 dataGridView1.DataSource = src;  
  47.             }  
  48.         }  
  49.     }  
  50. }  

これでとりあえず落ちなくはなった。
実際の業務処理では全項目ということはなく必ずSelectが入るだろうからあまり実害はないのかもしれないが、サンプルなどではありがちなパターンなので面倒は面倒だ。


逆に、ここらへんの性質を活かした階層構造対応のGridコントロールなんかがあれば便利なのにと思ったりもする。(サードパーティでは昔からあるが)


なんにしても初めからやけに躓いて時間を浪費してしまった・・・・。

0 件のコメント:

コメントを投稿

私が最近チェックした記事