SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

MonoでDBMS用データプロバイダを使う

Monoで他のDBも使ってみたよ!(Firebird、DB2編)

MonoでFirebird、DB2を使う

  • X ポスト
  • このエントリーをはてなブックマークに追加

DBアクセス

 データアダプタを使用したDBアクセスについての解説です。

接続確認

 接続確認用として、SELECT文を実行する以下のコード(C#)を使用します。

select1.cs
using System;
using System.Data;
using System.Data.Odbc;

namespace OdbcExample
{
  public class Select1
  {
    public static void Main(string[] args)
    {
      // 接続文字列
      string constr = "DSN=TestData_DB2;" +
"UID=sta;" +
"PWD=password";
// SQL文字列 string sstr = "SELECT ProductID, ProductName, Price, ProductDescription " + "FROM Products"; using (OdbcConnection dbcon = new OdbcConnection(constr)) { using (OdbcDataAdapter da = new OdbcDataAdapter(sstr, dbcon)) using (DataTable dt = new DataTable()) { // SELECT実行 da.Fill(dt); foreach (DataRow dr in dt.Rows) { Console.WriteLine("ID:{0} NAME:{1} PRICE:{2} DESCRIPTION:{3}", dr[0], dr[1], dr[2], dr[3]); } } } } } } /* * ビルド: * * gmcs select1.cs -r:System.Data.dll * * 実行: * * mono select1.exe * */
実行結果
$ mono select1.exe
ID:0 NAME:DB2 PRICE: DESCRIPTION:DB2 Express-C 9.5.2
ID:1 NAME:Clamp PRICE:12.48 DESCRIPTION:Workbench clamp
ID:50 NAME:Flat Head Screwdriver PRICE:3.17 DESCRIPTION:Flat head
ID:75 NAME:Tire Bar PRICE: DESCRIPTION:Tool for changing tires.
ID:3000 NAME:3mm Bracket PRICE:0.52 DESCRIPTION:

動作確認

 「System.Data.Odbc」はデータプロバイダファクトリ対応クラスを実装しています。そこで、各DBMSの動作確認で使用したコード(C#)を試しましたが、「Firebird編」と同様の追加だけでなく、SQL文内のパラメータの使用において、次の対応が必要でした。

  • SQL文内で使用できるのは位置指定パラメータ「?」のみ(名前付きパラメータは不可)
  • パラメータのDbTypeプロパティの明示的な指定
  • パラメータのSourceVersionプロパティの明示的な指定

 また、パラメータのDbTypeプロパティに「DbType.Decimal」を設定すると、

ERROR [HY104] [unixODBC][IBM][CLI Driver] CLI0143E  精度の値が無効です。 SQLSTATE=HY104

という例外が発生しました。そこで、Monoから提供されているSystem.Data.Odbcを調べてみると、DB2 CLI/ODBCドライバを使用する上で、多少の不具合があることが分かりました。
これまで動作確認を行った他のDBMSは、専用のデータプロバイダを使用できるので、System.Data.OdbcをDB2専用と割り切ると、以下の様な修正対応が考えられます。

System.Data.Odbcの修正

 上記例外は、パラメータに値をバインドする際に発生しています。原因は、精度(「Price decimal(7, 2)」でいうところの有効桁数7)に対応する引数に「0」を渡してるからで、「OdbcParameter.cs」に以下の修正することで対応が可能です。

OdbcParameter.cs から抜粋
...
internal void Bind (OdbcCommand command, IntPtr hstmt, int ParamNum)
{
...
    /* 修正前
    ret = libodbc.SQLBindParameter (hstmt, (ushort) ParamNum, (short) paramdir,
        _typeMap.NativeType, _typeMap.SqlType, Convert.ToUInt32 (Size),
        0, (IntPtr) _nativeBuffer, 0, _cbLengthInd);
    */

    // 修正後
    if (DbType == DbType.Decimal) {
        if (Precision == 0) {
            string s = Convert.ToString (Value);
            int l = s.Length;
            int i = s.IndexOf (".");

            if (i == -1) {
                Precision = (byte) l;
            }
            else {
                Precision = (byte) (l - 1);
                Scale = (byte) (l - (i + 1));
            }
        }

        ret = libodbc.SQLBindParameter (hstmt, (ushort) ParamNum, (short) paramdir,
            _typeMap.NativeType, _typeMap.SqlType, Convert.ToUInt32 (Precision),
            (short) Scale, (IntPtr) _nativeBuffer, 0, _cbLengthInd);
    }
    else {
        ret = libodbc.SQLBindParameter (hstmt, (ushort) ParamNum, (short) paramdir,
            _typeMap.NativeType, _typeMap.SqlType, Convert.ToUInt32 (Size),
            0, (IntPtr) _nativeBuffer, 0, _cbLengthInd);
    }
...
}
...

 さらに、パラメータに設定する値をDbTypeに応じてC言語データ型に変換しているのですが、DbType.Decimalに対応する変換について、以下の修正が必要です。

OdbcParameter.cs から抜粋
...
internal void CopyValue ()
{
...
    switch (_typeMap.OdbcType) {
...
    case OdbcType.BigInt:
        Marshal.WriteInt64 (_nativeBuffer, Convert.ToInt64 (Value));
            return;
    /* 修正前
    case OdbcType.Decimal:
    case OdbcType.Numeric:
        // for numeric, the buffer is a packed decimal struct.
        // ref http://www.it-faq.pl/mskb/181/254.HTM
        int [] bits = Decimal.GetBits (Convert.ToDecimal (Value));
        buffer = new byte [19]; // ref sqltypes.h
        buffer [0] = Precision;
        buffer [1] = (byte) ((bits [3] & 0x00FF0000) >> 16); // scale
        buffer [2] = (byte) ((bits [3] & 0x80000000) > 0 ? 2 : 1); //sign
        Buffer.BlockCopy (bits, 0, buffer, 3, 12); // copy data
        for (int j = 16; j < 19; j++) // pad with 0
        buffer [j] = 0;
        Marshal.Copy (buffer, 0, _nativeBuffer, 19); 
        return; 
     */
    case OdbcType.SmallInt:
        Marshal.WriteInt16 (_nativeBuffer, Convert.ToInt16 (Value));
        return;
    case OdbcType.TinyInt:
        Marshal.WriteByte (_nativeBuffer, Convert.ToByte (Value));
        return;
    // 修正後
    case OdbcType.Decimal:
case OdbcType.Numeric:
case OdbcType.Char: case OdbcType.Text: case OdbcType.VarChar: buffer = new byte [GetNativeSize ()]; nativeBytes = enc.GetBytes (Convert.ToString (Value)); Array.Copy (nativeBytes, 0, buffer, 0, nativeBytes.Length); buffer [buffer.Length-1] = (byte) 0; Marshal.Copy (buffer, 0, _nativeBuffer, buffer.Length); Marshal.WriteInt32 (_cbLengthInd, -3); return; case OdbcType.NChar: ... } ...

 修正前は、SQL_C_NUMERIC構造体を想定した変換を行っていますが、Linux上のDB2 CLI/ODBCドライバではSQL_C_NUMERICは使用できないようです。
その代わり、DB2 CLI/ODBCドライバは、文字C言語データ型:SQL_C_CHAR(例:"123.45\0"等)から10進数SQLデータ型:SQL_DECIMAL、SQL_NUMERIC(decimal(x, y))への変換をサポートしているので、文字列データと同様のC言語データ型変換を行うようにしました。

 また、DbType、SourceVersionプロパティの初期値についても修正を行いました(明示的な指定が必要だった部分の緩和)。

OdbcParameter.cs から抜粋
...
public OdbcParameter ()
{
    _cbLengthInd = new NativeBuffer ();
    ParameterName = String.Empty;
    IsNullable = false;
    SourceColumn = String.Empty;
    Direction = ParameterDirection.Input;

    /* 修正前
    _typeMap = OdbcTypeConverter.GetTypeMap (OdbcType.NVarChar);
     */

    // 修正後
    _typeMap = OdbcTypeConverter.GetTypeMap (OdbcType.VarChar);
    sourceVersion = DataRowVersion.Current;
}
...

 主な修正対応は以上です(修正を行ったSystem.Data.Odbc関連の資源をサンプルとして本稿に添付しました)。

次のページ
6.まとめ

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
MonoでDBMS用データプロバイダを使う連載記事一覧

もっと読む

この記事の著者

sta(エステーエー)

風来坊blog:sta.blockhead

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/3975 2009/08/28 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング