GUID

ODP.NETではGUIDをサポートしています。GUIDは、任意のGUID値を保持するのに十分な大きさのRAW(16)列に挿入できます。ただし、GUIDを適切に処理するには注意が必要です。これは、Guid(byte[ ])コンストラクタが使用され、Guid構造体のToByteArray()メソッドが呼び出されると、NET Guid構造がGUID値の整数ベースの部分に対してバイト値を逆の順序にするためです。

たとえば、入力パラメータとしてGuidがODP.NETに指定されている場合、ODP.NETはRAWバイトとしてOracle Databaseに値を渡す前に、ToByteArray()メソッドを呼び出します。GUIDが最初に次のバイト・シーケンスを持つ場合、

9D4E51F764A940E4854D667F0DD61093

GUIDのbyte[ ]表現が次のように変更されます

F7514E9DA964E440854D667F0DD61093

これは、RAW(16)列に挿入される前に、ODP.NETによって内部的に呼び出されるToByteArray()メソッドを呼び出すと、前述のように処理されるという意味です。

Oracle Databaseから同じ列値が取得されると、取得される値は次のようになります

F7514E9DA964E440854D667F0DD61093

このように、データベースで格納されます。OracleDataReaderオブジェクトでGetGuid()メソッドを呼び出すと、ODP.NETは、Guid(byte[ ])コンストラクタを使用してGuid構造を構築し、これを元のバイト・シーケンスに戻します。

9D4E51F764A940E4854D667F0DD61093

つまり、アプリケーション内のGUIDのバイト・シーケンスは、データベースに格納されている順序とまったく同じではないということです。言い換えると、次のようなリテラル・バイト値を使用してSQLを実行する場合

select * from ... where <raw/guid_column> = '9D4E51F764A940E4854D667F0DD61093';

行は返されません。次のものと同じGUIDが表に挿入されています。

F7514E9DA964E440854D667F0DD61093

アプリケーションで、次のような場合に、一致するGUIDを持つ行を問い合せることができます

  • Guid構造体を入力パラメータとして使用して、guid値をバインドする場合

  • Guid構造体上のToByteArray()メソッド呼出しから返されたbyte[]からSQLで使用されるバイト・リテラル値を作成する場合

アプリケーション開発者は、バイト・シーケンスが変更される可能性があるため、Guid(byte[ ])コンストラクタとGuid構造体のToByteArray()メソッドを呼び出す場合、注意する必要があります。次の単純なプログラムは、Guid(byte[ ])コンストラクタとToByteArray()メソッドの起動時に、GUID値の整数ベースの部分を戻す方法を示しています。

using System;
using System.Text;
using System.Data;
using Oracle.ManagedDataAccess.Client;

class T
{
  static string ByteToString(byte[] data)
  {
    StringBuilder sb = new StringBuilder(16);
    foreach (var b in data)
      sb.Append($"{b:X2}");
    return sb.ToString();
  }

  static void Main()
  {
    try
    {
      OracleConnection con = new OracleConnection("user id=<user id>;password=<password>;data source=<data source>");
      con.Open();

      // 
      // Generate a new GUID
      // 
      Guid guid = Guid.NewGuid();
      string original = guid.ToString().ToUpper().Replace("-", "");
      Console.WriteLine("Original Guid                     : " + original);

      // 
      // Drop the table
      // 
      OracleCommand cmd = new OracleCommand("drop table test_guid_table", con);
      try { cmd.ExecuteNonQuery(); } catch {}

      // 
      // Create the table
      // 
      cmd.CommandText = "create table test_guid_table (col1 RAW(16), col2 VARCHAR2(64))";
      cmd.ExecuteNonQuery();
      
      // 
      // Insert the newly generated GUID to the DB
      // 
      cmd.CommandText = "insert into test_guid_table values (:1, 'new guid')";
      cmd.Parameters.Add(string.Empty, OracleDbType.Raw);
      cmd.Parameters[0].Value = guid;
      cmd.ExecuteNonQuery();

      // 
      // Query from the test table
      // 
      cmd.CommandText = "select * from test_guid_table";
      OracleDataReader reader = cmd.ExecuteReader();

      while (reader.Read())
      {
        //
        // Get the RAW data as byte[]
        //
        byte[] guid_byte_array = (byte[])reader.GetValue(0);
        Console.WriteLine("GetValue() as byte[] / as-is in DB: " + ByteToString(guid_byte_array));
        Console.WriteLine();
       
        //
        // Get the RAW data as Guid then convert to byte[]
        //
        Guid retrieved_guid = (Guid)reader.GetGuid(0);
        byte[] retrieved_guid_byte_array = retrieved_guid.ToByteArray();
        Console.WriteLine("GetGuid() then Guid.ToString()    : " + retrieved_guid_byte_array.ToString());
        Console.WriteLine("GetGuid() then Guid.ToByteArray() : " + ByteToString(retrieved_guid.ToByteArray()));
      }

      //
      // Find a matching row by binding the original GUID as-is
      //
      cmd.Parameters.Clear();
      Console.WriteLine("\nGuid Input Parameter              : " + ByteToString(guid.ToByteArray()));
      cmd.CommandText = "select count(*) from test_guid_table where col1 = :1";
      cmd.Parameters.Add(string.Empty, OracleDbType.Raw);
      cmd.Parameters[0].Value = guid;
      reader = cmd.ExecuteReader();
      while (reader.Read())
      {
        Console.WriteLine("Rows found by binding GUID        : " + (decimal)reader.GetValue(0));
      }

      //
      // Find a matching row by binding a byte[] from the original GUID
      //
      byte[] byte_array_param = guid.ToByteArray();
      Console.WriteLine("\nbyte[] Input Parameter            : " + ByteToString(byte_array_param));
      cmd.CommandText = "select count(*) from test_guid_table where col1 = :1";
      cmd.Parameters.Clear();
      cmd.Parameters.Add(string.Empty, OracleDbType.Raw);
      cmd.Parameters[0].Value = byte_array_param;
      reader = cmd.ExecuteReader();
      while (reader.Read())
      {
        Console.WriteLine("Rows found by binding byte[]      : " + (decimal)reader.GetValue(0));
      }

      //
      // Find a matching row by matching the binary/raw data (inline) using Guid.ToByteArray()
      //
      cmd.CommandText = "select count(*) from test_guid_table where col1 = '" + ByteToString(guid.ToByteArray()) + "'";
      Console.WriteLine("\nLiteral RAW (from byte array)     : " + ByteToString(guid.ToByteArray()));
      cmd.Parameters.Clear();
      reader = cmd.ExecuteReader();
      while (reader.Read())
      {
        Console.WriteLine("Rows found by inlined data        : " + (decimal)reader.GetValue(0));
      }

      //
      // Find a matching row by matching the binary/raw data (inline) using Guid.ToString()
      //
      cmd.CommandText = "select count(*) from test_guid_table where col1 = '" + original + "'";
      Console.WriteLine("\nLiteral RAW (from string)         : " + original);
      cmd.Parameters.Clear();
      reader = cmd.ExecuteReader();
      while (reader.Read())
      {
        Console.WriteLine("Rows found by inlined data        : " + (decimal)reader.GetValue(0));
      }
    } catch (Exception ex) { Console.WriteLine(ex); }
  }
}

サンプル・コードからのサンプル出力は次のようになります。

Original Guid                     : D54909F4169541CFA919F6752414909F
GetValue() as byte[] / as-is in DB: F40949D59516CF41A919F6752414909F

GetGuid() then Guid.ToString()    : System.Byte[]
GetGuid() then Guid.ToByteArray() : F40949D59516CF41A919F6752414909F

Guid Input Parameter              : F40949D59516CF41A919F6752414909F
Rows found by binding GUID        : 1

byte[] Input Parameter            : F40949D59516CF41A919F6752414909F
Rows found by binding byte[]      : 1

Literal RAW (from byte array)     : F40949D59516CF41A919F6752414909F
Rows found by inlined data        : 1

Literal RAW (from string)         : D54909F4169541CFA919F6752414909F
Rows found by inlined data        : 0

ノート:

テストを実行するたびに、新しいGUIDまたは異なるGUIDが生成されます。