OracleのBlob型データカラムを対象に、データ取得・データ更新を行う。

Oracleを扱う機会があったので、ポストします。
OracleのBlob型のデータカラムを、C#側からアップデートしようとした際に、エラーが発生する場合がありました。
アップデートが行われる場合もあれば、行われない場合もある、といった具合です。

エラー内容は

[ ORA-08177: このトランザクションのアクセスをシリアル化できません ]

む……。ちゃんとOracle用の接続Objectが破棄できてないっぽいエラーです。

他のテーブルへのCUDは問題なく発行できていたので、BLOB型に対しての操作が、NGな状況です。

TransactionクラスのDispose、ConnectionクラスのClose、Dispose、すべて発行しているはずなのになぜだろう?

そもそもBLOB型って、普段使っていなかったので、イマイチ理解できていなかったので、内容確認してみることにしました。

参考
ラージ・オブジェクトの概要

LOB記憶域

↑を読んでのまとめ
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からは卒業したいので、引き続き、調査、検証を行う予定です。

進展があれば追記します。