OracleのBlob型データカラムを対象に、データ取得・データ更新を行う。
Oracleを扱う機会があったので、ポストします。
OracleのBlob型のデータカラムを、C#側からアップデートしようとした際に、エラーが発生する場合がありました。
アップデートが行われる場合もあれば、行われない場合もある、といった具合です。
エラー内容は
[ ORA-08177: このトランザクションのアクセスをシリアル化できません ]
む……。ちゃんとOracle用の接続Objectが破棄できてないっぽいエラーです。
他のテーブルへのCUDは問題なく発行できていたので、BLOB型に対しての操作が、NGな状況です。
TransactionクラスのDispose、ConnectionクラスのClose、Dispose、すべて発行しているはずなのになぜだろう?
そもそもBLOB型って、普段使っていなかったので、イマイチ理解できていなかったので、内容確認してみることにしました。
↑を読んでのまとめ
○OracleのBLOBは、ロケーターと値で構成されてる。
○ロケーターはLOBデータの参照情報を持っている。
○Selectで取得できるのは、そのロケーター
ただ、11gだと、Selectで、値のByteが戻されるっぽい。
※ 8KB程度の画像で試しただけなので、容量に上限があるかもしれない。
Updateは、一部のBLOB用のAPI以外は駄目っぽい。
今回エラーが出力されているケースは、、C#側からのUpdate文の発行で、「更新されることがある」ものの、「接続が正確に破棄されていない」挙動になっています。
※ たぶん、こんな感じ
更新対象テーブル:
TableHoge(BLOBデータ型カラムを持つテーブル)
1.TableHogeに対してUpdate文を発行する
↓↓↓↓
TableHogeが更新される。
↓↓↓↓
トランザクションのコミット発行
↓↓↓↓
TableHogeの更新が確定される。
↓↓↓↓
コネクションクローズ処理発行
↓↓↓↓
DBへの接続が閉じられる(コネクションプールは残ってるけど。)
↓↓↓↓
TableHogeのBLOBデータ型カラムの実体側は、接続残りっぱなしっぽい
○どうするか
正道としては、まずOracleDataReaderで、ロケーターをゲットする。
→ゲットしたロケーターから、BLOBの値を取得したり、格納したりする。
で一応解決するようです。
せっかく、OracleCommandさん、DataReaderさんとお別れできていたのに、また関わることになってしまいました。
オブジェクトと、RDBってそもそも思想が違うので、できれば色々お別れしたままにしておきたかったのですが……。
ということでコードです。
OracleConnnectクラス、的なライトなクラスを用意して対応しています。
基本はDapper使って、SQL発行するようなシンプルな構造です。
propertyは以下のような定義です。
DapperでPostgreSQLの接続クラスを作った時と、基本のノリは変わりません。
IdbConnectionさんたちに設定するオブジェクトが、PostgreSQL用からOracleに変わるだけです。
ただし、OracleCommandだけは、IdbCommandクラスで定義してしまうと、GetOracleBlobメソッドが使えないので、OracleCommandで定義します。
IdbCommandクラスにしても、GetBytesメソッドからアクセスできる気がしますが、少々面倒だったので、今回は、GetOracleBlobメソッドを使って楽をします。
#region property /// <summary> /// オラクルコマンド /// </summary> private OracleCommand OracleCommand { get; set; } /// <summary> /// オラクルコネクション /// </summary> private IDbConnection OracleConnection { get; set; } #endregion
プロパティーの定義は抜粋です。実際にはIDbTransactionクラスとか、色々います。
DB接続やトランザクションの開始等は、メソッドで提供して、Serviceクラスから操作するイメージです。
public bool BlobSelect(string query, OracleParameter prameters, out byte[] value, out int id) { OracleCommand = new OracleCommand(); OracleCommand.Connection = (OracleConnection)OracleConnection; OracleCommand.CommandText = query; OracleCommand.Parameters.Add(prameters); var reader = OracleCommand.ExecuteReader(); reader.Read(); id = (int)(reader.GetOracleDecimal(0)); var blob = reader.GetOracleBlob(1); value = blob.Value; return true; } public bool BlobUpdate(string query, OracleParameter prameters, byte[] value) { OracleCommand = new OracleCommand(); OracleCommand.Connection = (OracleConnection)OracleConnection; OracleCommand.CommandText = query; OracleCommand.Parameters.Add(prameters); var reader = OracleCommand.ExecuteReader(); reader.Read(); var blob = reader.GetOracleBlob(0); blob.Write(value, 0, value.Count()); var updateDate = reader.GetOracleDate(1); updateDate = (OracleDate)DateTime.Now; var updateUser = reader.GetOracleString(2); updateUser = (OracleString)"HogeUser"; return true; }
Dapper経由で、BLOBに対してUpdateしたいなーと思うのですが、無名ブロックのPL/SQL書くことになりそうな気がします。
結局それってBLOBへのアクセスをPL/SQLでラップするだけなので、抵抗感があります。(C#のプロダクトコードに、PL/SQLの無名ブロックがちょいちょい混ざるのはイヤ)
という理由で、今回はC#側でBLOBへのアクセス部分書いたのですが、やはり、OracleCommandやDataReaderからは卒業したいので、引き続き、調査、検証を行う予定です。
進展があれば追記します。