TypeProviderでSQLのパラメータに型をつける

コンピュテーション式でADO.NETのトランザクションを表現するアイデアを使いつつ、パラメータありのSQLをTypeProviderを使って(比較的)型安全に実行する方法を考えてみました。

TypeProviderはF# 3.0で導入された、コンパイル時に型を生成する機能です。

こんなコードが動きます。

#r @"..\Tranq\bin\Debug\Tranq.dll"
#r @"bin\Debug\Tranq.Provider.dll"

open Tranq
open System.Data.SqlClient

let provider() =
  let config = "Data Source=.\SQLEXPRESS;Initial Catalog=SampleDB;Integrated Security=True"
  new SqlConnection(config) :> System.Data.Common.DbConnection

(* TypeProviderを使ってSQLとパラメータの型をカプセル化した型を作成 *)
type SelectByNameAndAge = Sql<"
  select * from person where name like @name and age >= @age","
  name: string; age : int">
 
(* トランザクションを実行するコンピューテション式 *)
let selectPersons n a = required {
  (* TypeProviderで作られた型を使ってSQLを実行 *)
  let! persons = Database.query <| SelectByNameAndAge(name = n, age = a)
  return persons }

(* コンピュテーション式を実行 *)
let result = selectPersons "j%" 20 |> runTx provider 

(* 実行結果を出力 *)
match result with
| Some persons -> 
  printfn "success"
  persons |> Seq.iter (printfn "%A")
| _ -> 
  printfn "failed"


ポイントはもちろん、独自のTypeProvider(Sql)で新しい型(SelectByNameAndAge)を定義しているところ。

type SelectByNameAndAge = Sql<"
  select * from person where name like @name and age >= @age","
  name: string; age : int">

TypeProviderにSQLSQL中のパラメータ(@nameと@age)の型を教えています。TypeProviderは指定されたパラメータをコンストラクタで受け取る新しい型を生成します。


SQLを実行するときは、TypeProviderで生成された型のインスタンスを生成してDatabase.queryに渡します。コンストラクタの呼び出しには名前つき引数を使うのもポイントです。

Database.query <| SelectByNameAndAge(name = name, age = age)

この仕組みの何がうれしいかというと、パラメータの「名前や型が間違ってないこと」と「過不足」について、コンパイル時にチェックできるんですね!


このライブラリ、Tranq(仮称)という名前で作っていて、超単純なシナリオが何とか動くぐらいでのレベルでソースコードの公開もしていないのですが、興味もってくれる人っていますかねー?
コンピュテーション式とTypeProviderを使えて結構満足したので、これ以上作るかはまだ決めてなかったりします。