DBアクセス
データアダプタを使用したDBアクセスについての解説です。
接続確認
接続確認用として、SELECT文を実行する以下のコード(C#)を使用します。
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」に以下の修正することで対応が可能です。
... 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に対応する変換について、以下の修正が必要です。
... 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プロパティの初期値についても修正を行いました(明示的な指定が必要だった部分の緩和)。
... 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関連の資源をサンプルとして本稿に添付しました)。