Обновление NetProvider [build 1183]. Поддержка System.IO.Stream и System.IO.TextReader

Привет всем.

На сайт загружена новая сборка (1183) .NET провайдера, в которой реализована полноценная поддержка объектов System.IO.Stream и System.IO.TextReader. Объекты этих классов можно:

  • передавать в качестве IN-значений параметров команды
  • получать через OUT-значения параметров команды
  • получать через методы GetStream и GetTextReader класса OleDbDataReader
  • использовать как для обычных колонок, так и для колонок с массивами.

При передаче и при получении объектов этих классов используются механизмы «поточной обработки данных». Иными словами — теперь через Stream и TextReader можно перекачивать любые объемы информации.

Поддержка System.IO.Stream
При работе с объектами класса System.IO.Stream, NetProvider задействует стандартный COM-интерфейс ISequentialStream. COM-объекты с этим интерфейсом передаются в IBProvider и получаются из него.

Поддержка System.IO.TextReader
При работе с объектами класса System.IO.TextReader, NetProvider задействует специализированный COM-интерфейс IIBP_SequentialStream_WideChar. COM-объекты с этим интерфейсом передаются в IBProvider и получаются из него.

Передача объектов в качестве IN-значений параметров
Здесь нет никаких тонкостей — нужно просто установить объекты в OleDbParameter.Value. И все. Дальше NetProvider сам выполнит все необходимые операции по упаковке этих .NET объектов в COM-объекты и передаст последние в OLEDB провайдер.

Единственное что стоит отметить — NetProvider не вызывает Dispose у установленных объектов после завершения работы с ними.

Получение объектов через OUT-значения параметров
Для получения объектов через OUT-значения параметров, требуется дополнительное конфигурирование со стороны пользовательского приложения:

//using xdb=lcpi.data.oledb;
//xdb.OleDbCommand cmd;

//cmd.Parameters[0] связан с OUT-параметром запроса

//Получение объекта с интерфейсом System.IO.Stream
cmd.Parameters[0].OutputBinding.Set(xdb.OleDbType.IUnknown,
                                    typeof(System.IO.Stream);

//Получение объекта с интерфейсом System.IO.TextReader
cmd.Parameters[0].OutputBinding.Set(xdb.OleDbType.IUnknown,
                                    typeof(System.IO.TextReader);

//Получение массива с объектами System.IO.Stream
cmd.Parameters[0].OutputBinding.Set(xdb.OleDbType.Array_IUnknown,
                                   typeof(System.IO.Stream);

//Получение массива с объектами System.IO.TextReader
cmd.Parameters[0].OutputBinding.Set(xdb.OleDbType.Array_IUnknown,
                                    typeof(System.IO.TextReader);

Первый аргумент, с определением OLEDB типа, передается в OLEDB провайдер, и указывает последнему что нужно вернуть COM-объект с представлением значения параметра команды. Тип, указываемый во втором аргументе, определяет класс объекта (или элемента массива), который должен вернуть .NET провайдер, и влияет на интерфейс запрашиваемого COM-объекта.

Получение объектов для значений колонок
DbDataReader из .NET 4.5 содержит два новых виртуальных метода GetStream и GetTextReader, которые переопределяются в OleDbDataReader нашего .NET провайдера.

В случае сборок под предыдущие версии .NET (3.5, 4.0), GetStream и GetTextReader определены как обычные (не виртуальные) методы класса OleDbDataReader.

Пример с IN/OUT параметрами
Давно хотелось написать маленький и лаконичный пример, который бы загружал в базу данных реальный файл. Если исключить «служебный» код в ниже приведенном примере, думаю, что мне это удалось.

В данном примере выполняется запрос «INSERT… RETURNING…» с двумя параметрами: IN и OUT. Через IN-параметр передается объект System.IO.FileStream. А через OUT-параметр получаем обратно наши данные в виде потока, содержимое которого потом сравнивается с оригиналом.

Отмечу, что в запросе используется нестандартное расширение IBProvider-а — «RETURNING… INTO…», позволяющее явно указать имя для OUT-параметра. После добавления этой функциональности, я реально на неё подсел и повсеместно использую её в своих тестах 🙂

using System;
using System.Data;

using xdb=lcpi.data.oledb;

namespace Sample_001__BlobAndStream
{
 class Program
 {
  private const string c_cn_str
   ="provider=LCPI.IBProvider.3;"
   +"location=localhost:d:\\database\\ibp_test_fb25_d3.gdb;"
   +"user id=gamer;"
   +"password=vermut;"
   +"named_param_prefix=@;"
   +"named_param_rules=1";

  private const string c_sql
   ="insert into BIN_BLOB_TABLE (BIN_DATA) values (@in)\n"
   +"returning BIN_DATA\n"
   +"into @out";

  //883 229 976 bytes
  private const string c_src_file
   ="d:\\Distrib\\MSSQL\\MS SQL 2008 Express\\"
   +"ru_sql_server_2008_r2_express_with_advanced_services_x64.exe";

  static void Main(string[] args)
  {
   try
   {
    Console.WriteLine("Connect to database...");
    using(var cn=new xdb.OleDbConnection(c_cn_str))
    {
     cn.Open();

     Console.WriteLine("Begin transaction ...");
     using(var tr=cn.BeginTransaction(IsolationLevel.RepeatableRead))
     {
      using(var cmd=new xdb.OleDbCommand(c_sql,cn,tr))
      {
       var stream1=new System.IO.FileStream(c_src_file,
                                            System.IO.FileMode.Open);
       
       cmd["@in"].Value
        =stream1;

       cmd["@out"].OutputBinding.Set(xdb.OleDbType.IUnknown,
                                     typeof(System.IO.Stream));

       //-----
       Console.WriteLine("Execute command [press any key]...");
       Console.ReadKey(false);

       cmd.ExecuteNonQuery();
      
       //-----
       stream1.Dispose();

       //-----
       Console.WriteLine("Get streams ...");

       stream1=new System.IO.FileStream(c_src_file,
                                        System.IO.FileMode.Open);
       
       var stream2=(System.IO.Stream)cmd["@out"].Value;

       //-----
       Console.WriteLine("out value type: {0}",
                         stream2.GetType().FullName);
       
       //-----
       Console.WriteLine("Compare data [press any key]...");
       Console.ReadKey(false);

       const int c_buf_size=256*1024;

       var buf1=new byte[c_buf_size];
       var buf2=new byte[c_buf_size];

       long total_cb=0;

       for(;;)
       {
        var c1=stream1.Read(buf1,0,c_buf_size);
        var c2=stream2.Read(buf2,0,c_buf_size);

        if(c1!=c2)
         throw new ApplicationException("not equal block sizes!");
       
        for(int i=0;i!=c1;++i)
        {
         if(buf1[i]!=buf2[i])
          throw new ApplicationException("not equal elements!");
        }//for i

        total_cb+=c1;

        if(c1==0)
         break;
       }//for

       stream1.Dispose();
       stream2.Dispose();

       Console.WriteLine("Total byte count: {0}",total_cb);
      }//using cmd

      Console.WriteLine("Commit transaction...");
      tr.Commit();
     }//using tr
    }//using cn
   }
   catch(Exception e)
   {
    Console.WriteLine("");
    Console.WriteLine("ERROR: {0} - {1}",e.Source,e.Message);
   }//catch

   Console.WriteLine("DONE [press any key]");
   Console.ReadKey(false);
  }//Main
 }//class Program
}//namespace Sample_001__BlobAndStream

Вывод:

Вывод работы тестового примера.

Ход выполнения программы в Process Explorer-e:


Screenshot вкладки

На этом пока все. Всем пока! 🙂

Leave a Comment