サポンテ 勉強ノート

サポンテの勉強ノート・読書メモなどを晒します。

SQL - LINQ コード対比

はじめに

統合言語クエリ - Wikipedia

.NET 3.5 で導入された新しい(当時)構文ですが、ほとんど使ったことがありませんでした。

理由としては以下のような感じになります。

使いどころが無い。

そもそも使いたいと強く感じる場面がありませんでした(少なくとも今までは)。入門記事を読んでみても、その魅力をずいぶん強調してはいるものの、適用シーンが思い浮かびません。

ネットのサンプルが C# ばかり

現在の職場では VB.NET 主流です。同じ .NET なので C# のコード査読も基本的には苦にならないはずなのですが、しかし LINQ は新しい(当時)概念であり、構文もこれまでと大きく異なる(ように見える)ため、同じようには読めません。

ネット上のサンプルに添えられた説明だけでは不足していてよくわからず、逆に詳細なものは理論が先行しすぎていて、実際の使い方がよくわからないという状況でした。

SQL 憎しの説明が多い

どうも「SQL を使わなくてもいい」とか「なじみの無い SQL に似た構文もあるけど、そっちは使わなくても書ける」といった説明が多く見受けられました。

私としては SQL の方がずっとなじみがあるものなので、そうした説明はむしろバリアになります。「SQL ではこう書くところ、LINQ ではこう書く」という、私にとって解りやすい説明がなかったのです。

また後日感じたことですが、C# では SQL に似たクエリ構文よりも、メソッドチェーンのように書ける構文の方がすっきりとしていて好まれるのに対して、VB ではクエリ構文の方がすっきりしています。この点では時代を経るごとに両者の文化の溝が深まっていきそうな感じがします。

使いたいところ

自分の思いつく使いどころは「SQL で採ってきたデータを、いろいろ編集してから集計したりソートしたりしたい」というものでした。既に SQL の世界を離れ .NET の世界にあるデータテーブル(System.Data.DataTable)に対して SQL を発行するような気楽なイメージで使いたいのです

複雑なサブシステムが多く、あちこちからデータを取得し、最終的に集計をしたい場合、SQL だけでは望むものが作れません。

また経験上、サブクエリを駆使してデータベースへの問い合わせ回数を減らすよりも、取得したいデータを VB.NET の世界に持ってきてからプログラムで操作をした方がパフォーマンスが出る場面も多かったので、そのような場面でも LINQ が威力を発揮すると考えました。

コード対比サンプル

ではその「SQL ではこう書くところ、LINQ ではこう書く」の対比サンプルを掲載しておきます。

ユースケースとしては、以下の SQL で行うような集計を、データテーブルに対して行うことを想定します。

SELECT
      theID         AS product_code
    , SUM(price)    AS summary_price
FROM theTable
WHERE 1=1
    AND discontinue = 0
    AND disabled_date IS NULL
GROUP BY
    theID
ORDER BY
    theID

続いてデータテーブルから同様に集計データを抽出する LINQ サンプルです。データテーブル「 dt 」には SELECT theID, price FROM theTable の結果のようなものが入っていると想定します。

Imports System.Data

' 中略

Dim dt as DataTable

' 中略

Dim olinq As Object

olinq = From dr As DataRow In dt
        Where 1 = 1 _
            AND dr("discontinue") = 0
            AND dr("disabled_date") Is DBNull.Value
        Order By dr("theID") Ascending
        Group By
            product_code = dr("theID")
        Into
            summary_price = Sum(CInt(dr("price")))
        Select New With {
            .ProductCode = product_code
            .SummaryPrice = summary_price
        }

For Each row As Object In olinq
    System.Diagnostics.Debug.Print(row.ProductCode & " " & row.summary_price)
Next

最後の Select 句の中で無名オブジェクトに初期化していますが、特定のクラスにも出来ます。

Class ProductObj
    Public ProductCode As String
    Public SummaryPrice As Integer
End Class

' 中略

olinq = ... ' 中略
        ' ... 略
        Select New ProductObj With {
            .ProductCode = product_code
            .SummaryPrice = summary_price
        }

For Each row As ProductObj In olinq
    System.Diagnostics.Debug.Print(row.ProductCode & " " & row.summary_price)
Next

免責

サンプルは、説明のために要点以外は省略しています。そのままでは動作しません。

終わりに

LINQ は今までと勝手が違うので、まずはなじみのある書き方で挙動を確認してから、理論的なものを追いかけていけば取っ付きやすいかなと思います。

やっと LINQ のスタート地点に立てた感じです。