.NETで作る!

.NETに関するあれこれ(C#、VB.NET)

Unity(DI)でDBに接続してみよう

SeviceLocator経由でInstanceを作成する場合、Unityに初期化処理をいろいろと任せることができます。 ということで、DB接続処理をUnityにやらせてみようと思います。

では、比較対象としてUnityを使用せずにDB接続するコードを書いてみましょう。

Private Sub DbConnection()
    Using cn As IDbConnection = New SqlClient.SqlConnection("Data Source=.\SQLEXPRESS;Initial Catalog=AdventureWorks2016CTP3;Integrated Security=SSPI")
        cn.Open()
        Using cmd As IDbCommand = New SqlClient.SqlCommand("select current_timestamp", cn)
            Dim d = cmd.ExecuteScalar
            Console.WriteLine(d)
        End Using
    End Using
End Sub

特に問題があるわけではないのですが、

  • DB接続文字列を毎回書くのは面倒だし、間違える
  • ネスト回数が深くてウザイ
  • SQLを投げるコードは書きたいがコネクション開閉コード毎回書くの面倒

などいろいろ問題がありそうです。

そういえば過去にこんな記事書いてましたわ…

mk3008net.hatenablog.com

上記は悪くないと思うのですが、無名関数使うとデバッグがしづらい(赤波下線がプロシージャ全体に付くのでどの行がおかしいのかわかりづらい)という欠点があるんですよね…

Unityを使うとこうなる

Private Sub DbConnectionWithUnity()
    Using cmd = ServiceLocator.Current.GetInstance(Of IDbCommand)
        cmd.CommandText = "select current_timestamp"
        Dim d = cmd.ExecuteScalar()
        Console.WriteLine(d)
    End Using
End Sub

Connectionに関するコードがなくなり8行で書いていたコードが6行になりました。 Usingのネストも1つ減って見やすいです。

ではUnityコンフィグも見てみましょう。

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">

  <alias alias="IDbConnection" type="System.Data.IDbConnection, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  <alias alias="IDbCommand"    type="System.Data.IDbCommand, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  <alias alias="SqlConnection" type="System.Data.SqlClient.SqlConnection, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  <alias alias="SqlCommand"    type="System.Data.SqlClient.SqlCommand, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

  <container>

    <register type="IDbConnection" mapTo="SqlConnection" name="sqlsvr">
      <constructor>
        <param name="connectionString" value="Data Source=.\SQLEXPRESS;Initial Catalog=AdventureWorks2016CTP3;Integrated Security=SSPI" />
      </constructor>
      <method name="Open" />
      <lifetime type="singleton" />
    </register>

    <register type="IDbCommand" mapTo="SqlCommand">
      <constructor>
        <param name="cmdText" value="mustoverride" />
        <param name="connection" dependencyName="sqlsvr" />
      </constructor>
    </register>

  </container>
</unity>

長いwですが、基本的に使いまわしがきくので一回書いてしまえば(コピペしてしまえば)大した手間ではありません。 ではConfigの内容を解説します。

※こちら The Unity Configuration Schema も参考ください。

alias

Unityが名前解決できるようIDbConnectionとかIDbCommandとかの実態(アセンブリとクラス名)を書いてあげます。 IDbConnectionとIDbCommandの部分は基本的に変える必要はないです。 今回の例はSQLServerを使用していますので、SqlConnectionとSqlCommandの実態を定義しています。 OracleODBCを使用する場合は変更してください。

register type="IDbConnection" mapTo="SqlConnection" name="sqlsvr"

Unity経由でIdbConnectionを「sqlsvr」という名前で名前解決する場合、実態は「SqlConnection」にしてください、という意味。 IDbCommandの名前解決時に参照するので何かしらの名前を付けています。(例では「sqlsvr」としました)

constructor

SqlConnectionコンストラクタの引数に渡す値を定義。DB接続文字列を渡せますので書いておきます。

method name="Open"

インスタンス生成時、ついでに呼んでほしいメソッドが定義できます。 上記の場合、IDbConnectionをUnityに名前解決してもらった際、コネクションがオープンされた状態でインスタンスがもらえます。 つまり、自分でDB接続をオープンする必要はありません。

lifetime type="singleton"

これは任意の属性ですが、DBコネクションはシングルトン(1個しかインスタンスがない)のほうが都合がよいので指定。 Webアプリはこの限りではないですが。

param name="cmdText" value="mustoverride"

SqlCommandのコンストラクターにcmdText(SQL文)の項目がありますが、SQL文は固定ではないので適当な文字を当てはめています。

param name="connection" dependencyName="sqlsvr"

こっちが本題です。DB接続に「dependencyName="sqlsvr"」を指定しています。 これは「sqlsvr」という名前で指定されている「IDbConnection」をセットするとしています。 コードで書くなら

ServiceLocator.Current.GetInstance(Of IDbConnection)("sqlsvr")

をセットしなさいという意味になります。 これのおかげでIDbCommandの名前解決をしようとすると、IDbConnectionが勝手に生成されて、かつDB接続もオープンになるわけです。

register type="IDbCommand" mapTo="SqlCommand"

IDbConnectionの時と違い「name」の要素を指定していません。 これは必要がなかった(DBの接続先が1つしかなかった)から省略しています。 名前を付けても構いませんが、その場合名前解時に名前を指定する必要があるのでご注意。

名前を付けない場合

ServiceLocator.Current.GetInstance(Of IDbCommand)

名前を付けた場合

ServiceLocator.Current.GetInstance(Of IDbCommand)(なまえ)

というわけでDB接続処理がすっきりしました!

トランザクションというでかい問題が残ってますけどそれはまたの機会に。

. .