F# + MySQL + Entity Framework


Entity Framework を F# から使う場合, SqlEntityConnection が便利です。

試しに UCSC の公開ゲノムデータベースに接続してみます。なお, conditions of use に Bot access and excessive program-driven use are not permitted とあるので気をつけましょう。

まず UCSC の公開ゲノムデータベースは MySQL です。 MySQL のコネクターが必要です。 SqlEntityConnection を利用する場合は GAC にインストールされてないといけないらしいので, MySQL Connector/Net からインストーラーをダウンロードしてインストールします。

その他 F# で SqlEntityConnection 型プロバイダーを使うために,必要なアセンブリーを参照します。

  • System.Configuration
  • System.Data
  • System.Data.Entity
  • EntityFramework
  • FSharp.Data.TypeProviders
  • MySql.Data
  • MySql.Data.Entity.EF6

準備ができたのでコードを書いていきます。

まず最初に接続情報を設定ファイルに記述します。ウェブアプリケーションの場合は Web.config に,コンソールアプリケーションの場合は App.config に以下を記述します。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
   <connectionStrings>
      <add name="hg38" providerName="MySql.Data.MySqlClient" connectionString="Server=genome-mysql.cse.ucsc.edu;Database=hg38;Uid=genome" />
   </connectionStrings>

   <system.data>
      <DbProviderFactories>
         <remove invariant="MySql.Data.MySqlClient" />
         <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data" />
      </DbProviderFactories>
   </system.data>
</configuration>

NuGet で MySql.Data.Entity をインストールした場合は system.data 要素の中身はインストールスクリプトにより追加されますので,実質 connectionStrings を追加するだけで済みます。

次に型プロバイダーの記述です。これは単純に接続文字列名を与えるだけで OK です。

module Genome

open Microsoft.FSharp.Data.TypeProviders

type internal Hg38 = SqlEntityConnection<ConnectionStringName = "hg38", Provider = "MySql.Data.MySqlClient", Pluralize = true>
let internal hg38 = Hg38.GetDataContext ()

これだけで,コンパイル時にデータベースからテーブル情報を持ってきてくれます。テーブル変更が起こった場合は,コンパイラーが気付いてくれます。ここに型プロバイダーの便利さがあります。ただし,必ずしも良いことばかりではなくて,コンパイル時に DB 接続を行い[A]テーブルチェックを行うので,必然的にコンパイル時間は長くなります。特に今回のデータベースはテーブル数が 200 以上あるため,マシンによってはコンパイルに数分かかってしまうかもしれません。

注意すべき点として,ここで定義する型は, internal 以下のアクセシビリティーでなければなりません。モジュールを作って他のモジュールから呼ぶことを考えれば必然的に internal 一択です。

ConnectionStringName パラメーターは,設定ファイルで指定した名前です。設定ファイルは ConfigFile パラメーターにより指定することも可能です。

Proivder パラメーターに true を設定すると,テーブル名を複数形にした名前のオブジェクトができます。例えば refGene というテーブルが, hg38.refGenes という名前でアクセスできます。このあとに書くクエリー式で for var in hg38.tableName という形式をとるので,テーブル名が複数形でアクセスできると変数名を自然に単数形にできるので,地味に嬉しいです。

最後に,型プロバイダーで作成した型を利用して,適当に処理を行ってみます。例えば染色体ごとの遺伝子数を求めるには以下のようにします。

open System.Linq
open Genome

query {
   for refGene in hg38.refGenes do
   groupBy refGene.chrom into g
   sortBy g.Key
   select (g.Key, g.Count ())
}
|> Seq.iter (fun (chrom, count) -> printfn "%s:\t%d" chrom count)

簡単ですね。

まとめ

Entity Framework を F# から使うと,以下のようなメリットがあります。

  • データベースの接続情報さえ与えておけば,それ以上のことはコンパイラーが良い感じに処理してくれる。

コード生成すると,そのコードをどう管理するかといった問題が生じますが,型プロバイダーだと何も考えなくて良いです。

一方,以下のようなデメリットもあります。

  • ドライバー DLL の GAC 登録が必要。
  • コンパイル時間が長くなる。

F# のコンパイラーがもう少し賢くて型プロバイダーの部分のビルドを適宜スキップしてくれれば,この問題は解決するのですが,現実問題として別の部分のコードを少しいじっただけでビルドが発生してしまいます。 InternalVisibleTo あたりをうまく使えばいけるのかもしれませんね。

脚注

  1. テーブルスキーマが手元にある場合は,必ずしも DB 接続を行わなくても良いです。 []