DataSetはLINQの夢を半分だけ見られるか?

1か月もブログ記事を書かないとサーバー側のキャッシュがおかしくなる(っぽい)ので、穴埋めに。

Visual Studio 2017には「LINQ to SQL」がないよ

ふと、書籍の改訂版を作っていて、2015から2017に移行するときにテンプレートに「LINQ to SQL」がないことに気付きました。

Visual Studio 2017

Visual Studio 2015

LINQ to SQLが何じゃ?と思うかもしれませんが(実際、何じゃ?という代物なんですが)、DataSetを使っていた後に C#/VB で LINQ が使えるようになって、今の Entity Framework が出る前に SQL Server を LINQ で扱うとという「非常にピンポイント」なコンポーネントだったんですね。確か、同時期に「EF Context」が出てたとおもうのですが、EF Contextのほうはデザイナがなくて、データベースからドラッグ&ドロップするにはこの「LINQ to SQL」のほうが(解説に)便利だったんですよ。

ところで、今「EF 6.x DbContext」を追加しようとすると「置換トークン ‘$edmxInputFile$’ を、生成元になる .edmx ファイルの実際の名前で上書きしてください。」と出るのは、不具合なのかUpdate不足なのか…不明ですが。それはさておき。

「LINQ to SQL」自体は、SQL Serverにしか対応していなくて、MySQLやSQLiteには使えないから当時から汎用性がないのは承知なのですが、逆に言えば SQL Server だけ使うことが決まっていればこれでも良い訳です。まあ、今から作るとなると、EF のほうを使ったほうがいいので、「ADO.NET Entity Data Model」を使ったほうがいいよねということになります。

今後 2017 のテンプレートとして追加されるかどうかは不明なのですが、まあディスコンという奴です。

DataSetってLINQができたっけ?

じゃあ、仕方がない古いところで DataSet って LINQ ってできたっけ?と思ってみたのがこんなところです。

古き良き時代?のDataSetを追加して、SQL Server から2つのテーブルをドラッグ&ドロップします。すると型付きの DataTable ができますよね。

でもって、3つのボタンを付けて動かしてみます。

private void button1_Click(object sender, EventArgs e)
{
    var ad = new DataSet1TableAdapters.ProductTableAdapter();
    var ta = ad.GetData();
    var q = from t in ta select t.Name;
    listBox1.DataSource = q.ToList();
}

private void button2_Click(object sender, EventArgs e)
{
    var ad = new DataSet1TableAdapters.ProductTableAdapter();
    var ta = ad.GetData();
    var q = from t in ta where t.Id == 2 select t.Name;
    listBox1.DataSource = q.ToList();
}

private void button3_Click(object sender, EventArgs e)
{
    var ad1 = new DataSet1TableAdapters.ProductTableAdapter();
    var tproduct = ad1.GetData();
    var ad2 = new DataSet1TableAdapters.StoreTableAdapter();
    var tstore = ad2.GetData();

    var q = from p in tproduct
            join s in tstore on p.Id equals s.ProductId
            select $"{p.Name}({s.Description}) {s.Stored}個";
    listBox1.DataSource = q.ToList();
}

動かしてみると、おお、なんか(一見)LINQが動いているように見えますね。キチンとテーブル結合(join)も動いているし。

なんだ、これって DataSet が進化して LINQ が使えるようになったんじゃん、と喜ぶの早いところで、実は ad.GetData() のところでテーブルの内容をごっそりもってきて単純に List 同士で join しているだけなんですね。なので、button3_Click メソッド内に書いてあるように、2つのテーブルをそれぞれ TableAdapter を使って GetData で全検索。そして、List 化された tproduct と tstore を join しているだけなので、メモリは膨大になります。まあ、今の PC だと 1万件位ならば大丈夫でしょうが、100万件位になると PC は固まるだろうし、そもそもネットワーク負荷が膨大。

素直に EF を使う

なので、素直に「ADO.NET Entity Data Model」を使おうって話です。
書き替えたのがこれ。

private void button1_Click(object sender, EventArgs e)
{
    var ent = new testdbEntities();
    var q = from t in ent.Product select t.Name;
    listBox1.DataSource = q.ToList();
}

private void button2_Click(object sender, EventArgs e)
{
    var ent = new testdbEntities();
    ent.Database.Log = sql => { System.Diagnostics.Debug.WriteLine(sql); };
    var q = from t in ent.Product where t.Id == 2  select t.Name;
    listBox1.DataSource = q.ToList();
}

private void button3_Click(object sender, EventArgs e)
{
    var ent = new testdbEntities();
    ent.Database.Log = sql => { System.Diagnostics.Debug.WriteLine(sql); };
    var q = from p in ent.Product
            join s in ent.Store on p.Id equals s.ProductId
            select $"{p.Name}({s.Description}) {s.Stored}個"; // ★
    listBox1.DataSource = q.ToList();
}

ent.Database.Log なところは、遅延実行される SQL をデバッグ出力しているところです。先の DataSet には、この Log なるところがない(そもそも List の結合なので SQL 文を発行してるわけではない)ところで区別がつきます。

あと、試してみると解りますが、★の部分は動きません。実行エラーになります。DataSet のときは、List の結合で C# の範囲内で動くから ToString 等の文字列関係のものは動くのですが、EF の場合は SQL を発行する(SQL Server側で動く)ので ToString 等の関数がないので「実行エラー」になります。こんなところでも、LINQ がどっちで動いているかどうかわかります。

でもって、DataSetはどうするのか?

よく覚えていないのですが、確か DataSet の型付きDataTableの生成の仕組みが変わって、System.Data.TypedTableBase を継承するようになったからですよね。

DataRowCollection に LINQ 機能を付与する ( ソフトウェア ) – 憂国なプログラマ – Yahoo!ブログ
https://blogs.yahoo.co.jp/hilapon/7600156.html

DataSet / DataTableに対してLINQを使う方法 – @kotyのブログ
http://koty.hatenablog.com/entry/20110524/1306249991

2009年頃の記事を見ると DataRowCollection は IEnumerable を継承していないので LINQ が動きませんとなっていますが、現時点での生成コードを見ると Rows のほうも IEnumerable を継承するように書き変わっています。

つまり「ひそかに DataSet は進化している」訳です。まあ、それでも EF のほうを使う訳ですが。

なので、データベースに直接アクセスしてデータを引っ張ってくる場合は EF を使った遅延実行が必須なのですが、アプリケーション内で扱うマスターデータとか定義値を羅列した enum タイプのデータを join する場合は、DataTable を使うという方法もアリですね。
よくやる、初回だけデータベースから拾ってきて combobox とか listbox とかデータバインドするというアレです。まあ、EF でも同じことはできるので、新規の場合は意味はないのですが、既存の DataTable コードを EF に直さなくても、自前キャッシュを作り直して LINQ を導入してみるのもありかな、と思った次第です。

カテゴリー: 開発, C# パーマリンク