Дата публикации: 25.09.2006
Обновлено: 19.08.2015

Работа с Firebird и InterBase в Visual Studio. Часть 1. Основы ADO.NET (c#)

Предисловие
Что такое OLE DB Provider?
Методы подключения к Firebird и InterBase
Доступ к Firebird, InterBase при помощи команд
Использование транзакций
Обработка ошибок
Свойства объектов Ole Db
Пул подключений
Отсоединенная модель. DataSet
Keywords: Firebird C#, Firebird 2.0, Firebird ADO Net провайдер, работа с Firebird, альтернатива Borland DataProvider (BDP), доступ к Firebird, подключение к Firebird

Предисловие

В данном руководстве будет описан способ работы с базами данных InterBase и Firebird в ADO .Net при помощи управляемого Ole Db провайдера.

Firebird (InterBase) можно использовать для систем практически любого уровня, начиная от однопользовательских настольных приложений со встраиваемой базой данных (Embed Database), до клиент-серверных приложений уровня корпорации.

Средства и технологии, используемые в статье:

Что такое OLE DB Provider?

Ole Db провайдер представляет собой драйвер для доступа к БД через стандартные OLE DB интерфейсы. OLE DB провайдеры можно использовать совместно со многими клиентскими библиотеками: ADO, dbGo (ADO Express), ADO .Net, и др.

Более детальное описание технологии OLE DB дано в статье: ODBC Firebird драйвер, ODBC InterBase или OLE DB?

Для доступа к Firebird и InterBase в качестве OLE DB драйвера можно использовать IBProvider. Он поддерживает автоматическую настройку на работу с Firebird и InterBase всех версий, кроме того стандарт OLE DB позволяет использовать его в качестве Firebird .Net провайдера, а так же InterBase .Net провайдера.

Пожалуй, это самый универсальный драйвер среди всех компонентов Firebird и InterBase, помимо ADO .Net, с его помощью можно разрабатывать сценарии автоматизации на скриптовых языках (VBScript, JavaScript), использовать в автоматизации Microfost Office, подключать базы Firebird (InterBase) к MS SQL Linked Server и к средствам Business Intelligence, и реализовывать множество других преимуществ, которые доступны для стандартных интерфейсов доступа.

Для дальнейшей работы скачайте и установите IBProvider Professional Edition.

Методы подключения к Firebird и InterBase

Параметры строки подключения для работы с Firebird, InterBase

Для доступа к Firebird, InterBase, а так же к любой другой БД из ADO .Net через OLE DB провайдер, необходимо подключить соответствующее пространство имен к проекту Visual Studio:

using System.Data.OleDb;

Управление подключением к Ole Db источникам данных осуществляется с помощью класса OleDbConnection. Самый простой способ подключения к базе данных — прямое указание строки подключения в конструкторе этого класса:

OleDbConnection con = new OleDbConnection(connectionString);
con.Open();
con.Close();

Для формирования строки подключения в Net 2.0 появился класс OleDbConnectionStringBuilder:

OleDbConnectionStringBuilder cb = new OleDbConnectionStringBuilder();
cb.Provider = «LCPI.IBProvider»;
cb.Add(«Location»,@»localhost:d:\Program Files\Firebird\examples\EMPLOYEE.FDB»);
cb.Add(«User ID», «sysdba»);
cb.Add(«Password», «masterkey»);
cb.Add(«ctype», «win1251»);
Console.WriteLine(cb.ToString())

Существует определенный набор свойств инициализации IBProvider-a, который необходимо установить перед выполнением соединения с БД:

Обязательные свойства инициализации (параметры подключения) IBProvider:
Свойство Описание
Location Путь к базе данных на сервере.
Provider Имя Ole Db провайдера
User ID Имя пользователя базы данных
Password Пароль пользователя
Ctype Кодировка определяет, символы какого национального алфавита будут использоваться. Для русского и английского алфавита можно использовать кодировку WIN1251

Некоторые необязательные свойства инициализации IBProvider:
Свойство Описание
Data Source Данное свойство используется для задания user friendly имени для базы данных, например «Employee DB». Если свойство Loсation не определено, то предполагается, что в Data Source указано расположение базы данных.
db_client_type Тип клиента сервера базы данных. Есть только в IBProvider v3.
db_client_library DLL с клиентом сервера
auto_commit Режим автоматического подтверждения транзакций. Для его включения в строке подключения необходимо указать “auto commit =true”.
role Роль пользователя

Более подробно о свойствах инициализации IBProvider

СОВЕТ
Всегда включайте в параметр Location имя сервера базы данных. Это позволит обеспечить совместимость со всем версиями Firebird.

Способы хранения строк подключения

В реальных приложениях никто не прописывает строки подключения к базе данных в коде. Гораздо эффективнее использовать для этой цели либо настройки приложения (технология Settings), либо отдельный файл подключения.

Для хранения параметров подключения в Windows существует специальный тип файлов Microsoft Data Link — это файл с расширением udl. С этим расширением ассоциирован универсальный редактор подключений. IBProvider поддерживает свои собственные табы, которые предоставляют удобный интерфейс для формирования параметров соединения. Для того чтобы использовать udl файл в своем приложении выполните следующие шаги:

  • Создайте пустой файл с расширением .udl
  • Откройте файл (Enter), появится связанный с данным расширением диалог для настройки подключения:
  • В списке OleDb провайдеров выберете IBProvider v3:
Data Link Firebird/InterBase
  • Задайте параметры подключения по аналогии с рисунком и нажмите кнопку «Проверить подключение»:
Data Link: IBProvider data source
  • На вкладке «Дополнительно» вы можете задать расширенные свойства подключения:
Data Link: IBProvider extended properties
  • Нажмите «Ок» для записи информации о подключении в файл

Теперь для того чтобы использовать подключение, описанное udl файлом достаточно явно или через OleDbConnectionStringBuilder задать свойство File Name

OleDbConnectionStringBuilder cb = new OleDbConnectionStringBuilder();
cb.FileName = AppDomain.CurrentDomain.BaseDirectory + @»\employee.udl»;
OleDbConnection con = new OleDbConnection(cb.ToString());
con.Open();

Второй способ хранения строки подключения — это поместить её в конфигурационный файл приложения:

В свойствах проекта выберите вкладку Settings и создайте новое свойство с именем ConnectionString и типом (Connection string):

ConnectionString settings

При редактировании свойства запустится встроенный в VS 2005 — 2008 редактор строки подключения:

VS 2005 Connection String editor
ПРИМЕЧАНИЕ
Если нажать на кнопку “Data Links” появится уже знакомый нам диалог конфигурации Microsoft Data Link

Для того чтобы прочитать строку подключения из файла конфигурации необходимо создать экземпляр класса настроек вашего приложения:

Properties.Settings s = new Properties.Settings();
//чтение свойства с именем ConnectionString
Console.WriteLine(s.ConnectionString);

Для облегчения написания примеров был создан класс ConnectionProvider, который инкапсулирует в себе все, описанные методы подключения.

Шифрование строки подключения. Data Protection API

Один из вариантов защитить строку подключения в своем конфигурационном файле это воспользоваться Data Protection API (DAPI). Начиная с Windows 2000, DAPI является частью операционной системы.

Допустим, нам необходимо зашифровать данные, хранящиеся в секции connectionStrings. Для этого мы воспользуемся классом DataProtectionConfigurationProvider:

Добавим к проекту ссылку на System.Configuration.dll и используем следующий код:

public void DataProtectionAPITest()
{
    // Описание: DATA PROTECTION API, шифрование строки подключения. c#, .Net провайдер
    try
    {
    //открываем секцию connectionStrings из App.config               
    Configuration config = ConfigurationManager.OpenExeConfiguration(
    System.Reflection.Assembly.GetExecutingAssembly().Location);

    ConnectionStringsSection section =
    config.GetSection(«connectionStrings») as ConnectionStringsSection;

    if (!section.SectionInformation.IsProtected)
       {
         // выполняем шифрование секции
         section.SectionInformation.ProtectSection(
         «DataProtectionConfigurationProvider»);
         // Сохраняем конфигурацию
         config.Save();
       }

    }
    catch (Exception ex)
    {
           Console.WriteLine(ex.Message);
    }

    // получаем строку подключения из зашифрованной секции
    Console.WriteLine(Properties.Settings.Default.ConnectionString);
}
ПРЕДУПРЕЖДЕНИЕ
Данных хранящиеся в секции могут быть расшифрованы только на том компьютере, на котором были зашифрованы. Таким образом, процедуру шифрования данных необходимо вызывать на компьютере конечного пользователя.

Данный пример как раз подходит для этих целей. При установке приложения мы помещаем в папку с программой ещё не зашифрованный App.Config. При первом запуске приложения данная процедура его зашифрует и, в последствии, программа будет работать уже с защищенной секцией. Можно так же вызывать процедуру шифрования во время установки приложения.

К сожалению, нет стандартных средств для защиты UDL файлов, поэтому стоит воспользоваться способом хранения защищенных строк подключения в файле конфигурации.

Команды

Команды предназначены для передачи запросов базе данных. Для Ole Db провайдеров команда реализуется классом OleDbCommand. Команда всегда выполняется для заданного открытого подключения к базе данных в контексте транзакции.

Для того чтобы выполнить запрос к базе данных необходимо выполнить следующую последовательность действий:

  • Создать подключение к БД и открыть его
  • Создать активную транзакцию из текущего подключения — метод OleDbConnection.BeginTransaction()
  • Создать объект OleDbCommand, либо используя один из вариантов перегруженного конструктора, либо метод OleDbConnection.CreateCommand()
  • Установить свойство команды Transaction, если оно не было задано в конструкторе
  • Задать текст команды CommandText
  • Для обращения к базе данных у команды есть три метода ExecuteScalar, ExecuteReader и ExecuteNonQuery.
  • Завершить транзакцию OleDbTransaction.Commit() или откатить OleDbTransaction.Rollback() и закрыть подключение.

ExecuteScalar

Возвращает единственное значение первой колонки первой строки. Остальные результаты игнорируются. Этот метод полезен для запросов, которые, к примеру, считают количество записей в таблице — соответственно возвращают только одно значение:

public void ExecuteScalarTest()
{
   // Описание: ExecuteScalar — получение единственного значения. Firebird, InterBase .Net провайдер (c#)
   OleDbConnection con = ConnectionProvider.CreateConnection();
   con.Open();
   OleDbTransaction trans = con.BeginTransaction();

   OleDbCommand cmd = new OleDbCommand(«select count(*) from employee», con, trans);
   Console.WriteLine(«Record count:» + cmd.ExecuteScalar().ToString());

   trans.Commit();
   con.Close();
}

ExecuteReader

Данный метод возвращает объект OleDbDataReader, которые по своему назначению очень близок объекту Recordset из классического ADO. Он использует однонаправленное ForwardOnly чтение данных, реализуя подсоединенную модель доступа. Таким образом, при его использовании необходимо наличие отрытого подключения к базе.

Навигация по строкам результирующего множества осуществляется при помощи метода Read(), который возвращает true, в случае если ещё остались строки и false в противном случае. При вызове метода команды ExecuteReader(), созданный им объект OleDbDataReader не спозиционирован на первой строке результирующего множества и для её прочтения необходимо сначала вызвать метод Read().

Наиболее удобным способом чтения данных из результирующего множества является использование метода Read() совместно с конструкцией while:

public void ExecuteReaderTest()
{
   // Пример: ExecuteReader через Firebird и InterBase oledb драйвер; работа с InterBase c#
   OleDbConnection con = ConnectionProvider.CreateConnection();
   con.Open();

   //Используем метод CreateCommand для создания команды
   OleDbCommand cmd = con.CreateCommand();
   cmd.Transaction = con.BeginTransaction();
   cmd.CommandText = «select * from employee»;
   OleDbDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);

   //чтение данных
   while (rdr.Read())
   {
      string tmp =«»;
      for(int i=0; i<rdr.FieldCount -1;i++)
      {
         if (tmp != «») tmp += «\t»;
         tmp += rdr[i].ToString();
      }

      Console.WriteLine(tmp);
   }

   rdr.Close();
   //после вызова OleDbDataReader.Close() подключение к БД будет закрыто     
   Assert.AreEqual(ConnectionState.Closed,con.State);
}
ПРИМЕЧАНИЕ
Обратите внимание что после вызова метода OleDbDataReader.Close(), подключение к базе данных будет закрыто. Это произошло потому, что я применил перегруженный метод ExecuteReader() с заданным параметром CommandBehavior.CloseConnection. По умолчанию после выполнения метода OleDbDataReader.Close() подключение к базе данных остается открытым.

ExecuteNonQuery

Метод применяется для выполнения запросов, которые возвращают количество обработанных записей, таких как insert, update, delete, а так же для выполнения хранимых процедур, результат которых помещается в OUT параметры команды:

public void ExecuteNonQueryTest()
{
    //Пример ExecuteNonQuery через Firebird OLE DB драйвер, .Net провайдер (c#)
    OleDbConnection con = ConnectionProvider.CreateConnection();
    con.Open();
    OleDbTransaction trans = con.BeginTransaction();

    //INSERT         
    OleDbCommand cmd = new OleDbCommand(
    «insert into country (country,currency) values(:country,:currency) «,
    con, trans);

    cmd.Parameters.AddWithValue(«country», «Russia»);
    cmd.Parameters.AddWithValue(«currency», «Kopec»);

    //количество обработанных строк
    Assert.AreEqual(1, cmd.ExecuteNonQuery());

    //UPDATE
    cmd = new OleDbCommand(
    «update country set currency=:currency where country =:country», con, trans);

    cmd.Parameters.AddWithValue(«currency», «Rouble»);
    cmd.Parameters.AddWithValue(«country», «Russia»);

    //количество обработанных строк
    Assert.AreEqual(1, cmd.ExecuteNonQuery());

    //DELETE
    cmd = new OleDbCommand(
    «delete from country where country =:country», con, trans);

    cmd.Parameters.AddWithValue(«country», «Russia»);

    //количество обработанных строк
    Assert.AreEqual(1, cmd.ExecuteNonQuery());

    trans.Commit();
    con.Close();
}

Параметры команд

В большинстве случаев при выполнении команды требуется задать её параметры. Параметры добавляются в коллекцию Parameters. Они могут быть именованные и позиционные. Пример команды с позиционными параметрами:

insert into country (country,currency) values(?,?)

С именованными:

insert into country (country,currency) values(:country,:currency)

IBProvider сам умеет формировать список параметров, производя анализ SQL выражения. Но, к сожалению, в ADO .Net необходимо вручную добавлять эти параметры, т.к. команда не запрашивает их описание у Ole Db провайдера. Если вспомнить ADO, то в нем список параметров прекрасно формировался без необходимости вмешиваться в этот участок кода.

Для того чтобы добавить параметр нужно воспользоваться:

  • для добавления именованного параметра и значения метод AddWithValue()
  • для добавления как именованных, так и неименованных параметров перегруженный метод Add()

Если не указан тип параметра, он будет добавлен с Ole Db типом VarWChar, что соответствует .Net типу string, что кажется разумным. Об этом не стоит беспокоиться т.к. IBProvider корректно обрабатывает приведение любых типов Firebird.

Нельзя не сказать о существующих ограничениях при использовании именованных параметров совместно с OleDbCommand. В MSDN написано, что именованные параметры поддерживаются только для поставщиков данных MSSQL и Oracle, а для поставщиков данных Ole Db и ODBC поддерживаются только позиционные параметры. Использовать именованные параметры все же можно, но их добавление в коллекцию Parameters необходимо осуществлять в том же порядке, в каком они следуют в запросе. К примеру, если текст команды:

update country set currency=:currency where country =:country

то сначала необходимо добавить параметр currency, а потом country:

cmd.Parameters.AddWithValue(«currency», «Rouble»);
cmd.Parameters.AddWithValue(«country», «Russia»);

Задавать значения параметров можно уже в произвольном порядке:

cmd.Parameters[«country»].Value = «Latvia»;
cmd.Parameters[«currency»].Value = «Lat»;

Вызов хранимых процедур Firebird c# (InterBase c#)

Существуют два способа обработки результатов хранимых процедур:

  • хранимая процедура возвращает результирующее множество
  • результат выполнения хранимой процедуры помещается в OUT параметры команды

Для первого способа используется обычная SQL-инструкция:

 select * from stored_procedure_name(…)

Результат её выполнения обрабатывается при помощи объекта OleDbDataReader:

public void StoredProcedureResultSetTest()
{
    // Описание: SELECT * FROM STORED PROCEDURE, ExecuteReader, ResultSets, Firebird c#, InterBase .Net провайдер
    OleDbConnection con = ConnectionProvider.CreateConnection();
    con.Open();
    OleDbTransaction trans = con.BeginTransaction();

    //select stored procedure in params 
    OleDbCommand cmd_in_params = new OleDbCommand(«select cust_no from CUSTOMER», con, trans);

    //select mail label through the stored procedure
    OleDbCommand cmd_stored_proc = new OleDbCommand(«select * from mail_label(:cust_no)», con, trans);

    //add one IN parameter                     
    cmd_stored_proc.Parameters.Add(«cust_no», OleDbType.Integer);

    //execure reader
    using (OleDbDataReader rdr = cmd_in_params.ExecuteReader(CommandBehavior.CloseConnection))
    {
      //for each customer No  
      while (rdr.Read())
      {
          cmd_stored_proc.Parameters[«cust_no»].Value = rdr[«cust_no»];
          using (OleDbDataReader rdr_out = cmd_stored_proc.ExecuteReader())
          {
             Console.WriteLine(«Customer №» + rdr[«cust_no»]);
             while (rdr_out.Read())
                    for (int i = 0; i < rdr_out.FieldCount; i++)
                      Console.WriteLine(rdr_out.GetName(i) + «=» + rdr_out[i]);

            Console.WriteLine();
          }
      }

    }
}

Второй способ — вызов хранимой процедуры через инструкцию execute procedure:

 execute procedure stored_procedure_name

Результат выполнения помещается в OUT параметры команды, которые предварительно необходимо создать:

public void StoredProcedureOUTParamsTest()
{
   // Описание: EXECUTE PROCEDURE, out parameters, InterBase c#, Firebird .Net провайдер, IBProvider
   OleDbConnection con = ConnectionProvider.CreateConnection();
   con.Open();
   OleDbTransaction trans = con.BeginTransaction();

   //select in params 
   OleDbCommand cmd_in_params =
     new OleDbCommand(«select cust_no from CUSTOMER», con, trans);

   //STORED PROCEDURE
   OleDbCommand cmd_stored_proc =
     new OleDbCommand(«execute procedure mail_label(:cust_no)», con, trans);

   //IN parameter
   cmd_stored_proc.Parameters.Add(«cust_no», OleDbType.BSTR);
   //OUT parameters
   cmd_stored_proc.Parameters.Add(«line1», OleDbType.BSTR).Direction = ParameterDirection.Output;
   cmd_stored_proc.Parameters.Add(«line2», OleDbType.BSTR).Direction = ParameterDirection.Output;
   cmd_stored_proc.Parameters.Add(«line3», OleDbType.BSTR).Direction = ParameterDirection.Output;
   cmd_stored_proc.Parameters.Add(«line4», OleDbType.BSTR).Direction = ParameterDirection.Output;
   cmd_stored_proc.Parameters.Add(«line5», OleDbType.BSTR).Direction = ParameterDirection.Output;
   cmd_stored_proc.Parameters.Add(«line6», OleDbType.BSTR).Direction = ParameterDirection.Output;

   //execure reader
   using (OleDbDataReader rdr = cmd_in_params.ExecuteReader())
   {
       //for each customer No  
       while (rdr.Read())
       {
         cmd_stored_proc.Parameters[«cust_no»].Value = rdr[«cust_no»];
         cmd_stored_proc.ExecuteNonQuery();

         Console.WriteLine(«Customer №» + rdr[«cust_no»]);
         Console.WriteLine(cmd_stored_proc.Parameters[«line1»].Value);
         Console.WriteLine(cmd_stored_proc.Parameters[«line2»].Value);
         Console.WriteLine(cmd_stored_proc.Parameters[«line3»].Value);
         Console.WriteLine(cmd_stored_proc.Parameters[«line4»].Value);
         Console.WriteLine(cmd_stored_proc.Parameters[«line5»].Value);
         Console.WriteLine(cmd_stored_proc.Parameters[«line6»].Value);
         Console.WriteLine(«»);
       }
   }

   trans.Commit();
   con.Close();
}

Еще одной разновидностью второго способа вызова хранимой процедуры является — указание имени процедуры в тексте запроса и установка значения OleDbCommand.CommandType = StoredProdedure

В следующем примере хранимая процедура увеличивает значение генератора на единицу и возвращает полученное значение через OUT-параметр:

 CREATE PROCEDURE Next_Id
 RETURNS(Id Integer)
 AS
 BEGIN
      Id = GEN_ID(GET_TEST1,1);
 END

Вызов хранимой процедуры Firebird c#:

public void GetSingleValueFromSP()
{
    // Description: GEN_ID, CommandType.StoredProcedure, out parameters,
    // Firebird, InterBase, IBProvider, Firebird .Net provider
    using (OleDbConnection cn = ConnectionProvider.CreateConnection())
    {
        cn.Open();
        using (OleDbTransaction trans = cn.BeginTransaction())
        {

            OleDbCommand cmd = cn.CreateCommand();
            cmd.Transaction = trans;

            // 1. create generator
            cmd.CommandText = "CREATE GENERATOR GET_TEST1";
            cmd.ExecuteNonQuery();

            cmd.CommandText = "SET GENERATOR GET_TEST1 TO 102";
            cmd.ExecuteNonQuery();

            // 2. create stored proc
            cmd.CommandText = @"CREATE PROCEDURE Next_Id
                            RETURNS(Id Integer)
                            AS
                            BEGIN
                              Id = GEN_ID(GET_TEST1,1);
                            END";
            cmd.ExecuteNonQuery();

            //3. set command type and procedure name
            cmd.CommandType = System.Data.CommandType.StoredProcedure;
            cmd.CommandText = "NEXT_ID";

            OleDbParameter param = cmd.Parameters.Add("ID", OleDbType.BSTR);
            param.Direction = System.Data.ParameterDirection.Output;
            cmd.ExecuteNonQuery();
            cmd.Transaction.Commit();

            Console.WriteLine(":{0}", param.Value);
        }

        // 4. drop stored proc and generator
        using (OleDbTransaction trans = cn.BeginTransaction())
        {
            OleDbCommand cmd = cn.CreateCommand();
            cmd.Transaction = trans;

            cmd.CommandText = @"DROP GENERATOR GET_TEST1";
            cmd.ExecuteNonQuery();

            cmd.CommandText = @"DROP PROCEDURE Next_Id";
            cmd.ExecuteNonQuery();

            trans.Commit();
        }

        cn.Close();
    }
}

Доступ к массивам InterBase, Firebird

ADO .Net может работать с любыми типами данных. Для тех типов Ole Db, у которых нет прямого отображения на типы данных .Net используется тип данных DBTYPE_VARIANT. Массивы относятся как раз к таким типам.

Следующий пример демонстрирует чтение и запись массива из 5 элементов:

public void ArrayReadWriteTest()
{
    //Описание: МАССИВЫ Firebird, InterBase, чтение и запись массивов c#, ADO Net
    OleDbConnection con = ConnectionProvider.CreateConnection();
    con.Open();
    OleDbTransaction trans = con.BeginTransaction();

    OleDbCommand cmd = new OleDbCommand(
    «select job_code, job_grade, job_country, job_title, language_req from job»,
    con, trans);

    OleDbCommand cmd_upd = new OleDbCommand(
    «update job set language_req=:language_reg where \n» +
    «job_code=:job_code and job_grade=:job_grade and job_country=:job_country»,
    con, trans);

    cmd_upd.Parameters.Add(«language_req», OleDbType.Variant);
    cmd_upd.Parameters.Add(«job_code», OleDbType.BSTR);
    cmd_upd.Parameters.Add(«job_grade», OleDbType.BSTR);
    cmd_upd.Parameters.Add(«job_country», OleDbType.BSTR);

    using (OleDbDataReader rdr = cmd.ExecuteReader())
    {
        while (rdr.Read())
        {
        Console.WriteLine(«JOB TITLE:» + rdr[«job_title»].ToString());

        //чтение массива
        object lang_obj_arr = rdr[«language_req»];

        if (lang_obj_arr != DBNull.Value)
        {
            //преобразование к массиву
            //используем Array.CreateInstance для создания массива
            //из 5 элементов, с адресацией начиная с 1-го элемента, а не с 0
            short arr_lower_bound = 1;

            Array lang_str_arr = Array.CreateInstance(typeof(string), new int[] {5}, new int[] { arr_lower_bound });

            //копирование элементов в массив                         
            ((Array)lang_obj_arr).CopyTo(lang_str_arr, arr_lower_bound);

            for (int i = arr_lower_bound; i < lang_str_arr.Length + arr_lower_bound; i++)
            {
              //усечение символа \n на концах элементов массива
  	          string trimmed_value = lang_str_arr.GetValue(i).ToString().Replace(«\n», «»);
              lang_str_arr.SetValue(trimmed_value, i);

              //вывод значения
              if (lang_str_arr.GetValue(i).ToString() != «»)
                  Console.WriteLine(lang_str_arr.GetValue(i));
            }

            //запись новых значений элементов массива без символа \n                       
            cmd_upd.Parameters[«language_req»].Value = lang_str_arr;
            cmd_upd.Parameters[«job_code»].Value = rdr[«job_code»];
            cmd_upd.Parameters[«job_grade»].Value = rdr[«job_grade»];
            cmd_upd.Parameters[«job_country»].Value = rdr[«job_country»];

            //предача изменений в БД
            Assert.IsTrue(cmd_upd.ExecuteNonQuery() == 1);
         }
         else
            Console.WriteLine(«No language specified»);

         Console.WriteLine(«»);
       }
   }

   //откат сделанных изменений         
   trans.Rollback();
   con.Close();
}
ПРИМЕЧАНИЕ
В примере использован базовый класс Array и метод CreateInstance для создания массива строк. В C# адресация массивов начинается с нулевого элемента, а в данном случае в базе данных записан массив, который проиндексирован начиная с первого элемента. Array.CreateInstance() позволяет указать нижнюю границу массива элементов. В случае массивов с нулевой адресацией достаточно использования типизированных наследников класса Array например string[], int[] и т.д.

Работа с BLOB полями в Firebird, InterBase

IBProvider поддерживает работу с двумя типами BLOB полей: содержащих текст и бинарные данные. Благодаря встроенному в IBProvider конвертеру типов, работа с BLOB полями происходит так же, как и с обычными типами данных:

public void BLOBReadWriteTest()
{
    // Описание: Работа с BLOB полями, чтение и запись блобов, Firebird c#, InterBase ADO Net
    OleDbConnection con = ConnectionProvider.CreateConnection();
    con.Open();
    OleDbTransaction trans = con.BeginTransaction();

    //BLOB Read command
    OleDbCommand cmd = new OleDbCommand(
    «select proj_id, proj_name,proj_desc from project», con, trans);

    //BLOB write command			
    OleDbCommand cmd_update = new OleDbCommand(
    «update project set proj_desc=:proj_desc where proj_id=:proj_id», con, trans);

    //create parameters with BSTR type
    cmd_update.Parameters.Add(«proj_desc», OleDbType.BSTR);
    cmd_update.Parameters.Add(«proj_id», OleDbType.BSTR);

    using (OleDbDataReader rdr = cmd.ExecuteReader())
    {
       while (rdr.Read())
       {
             //чтение BLOB 
             Console.WriteLine(«PROJECT: « + rdr[«proj_name»].ToString());
             Console.WriteLine(rdr[«proj_desc»].ToString());

             //запись BLOB
             cmd_update.Parameters[«proj_id»].Value = rdr[«proj_id»];

             //каждый раз меняем регистр данных в BLOB поле
             string new_project_description = rdr[«proj_desc»].ToString();
             if (new_project_description.ToUpper() != new_project_description)
                 new_project_description = new_project_description.ToUpper();
             else
                 new_project_description = new_project_description.ToLower();

             cmd_update.Parameters[«proj_desc»].Value = new_project_description;
             Assert.AreEqual(1, cmd_update.ExecuteNonQuery());
       }
    }

    trans.Commit();
    con.Close();
}

Заметьте — тип параметров команды обновления установлен в OleDbType.BSTR. В этом случае провайдер корректно распознает тип параметров и произведет их преобразование к типам базы данных.

СОВЕТ
В примере OleDbDataReader использован совместно с конструкцией using. Он поддерживает интерфейс IDispose и после завершения работы сам позаботится о своем закрытии, а если в метод OleDbCommand.ExecuteReader() передать значение CommandBehavior.CloseConnection, то так же будет закрыто подключение к базе данных.

В качестве еще одного примера работы с BLOB полями в Firebird (InterBase), я хочу предложить вам, ставший уже классическим, пример записи рисунка в BLOB поле.

MARS — Multiple Active Result Sets

В ADO .Net 2.0 появилась «новая» технология, которая получила название MARS. В Net Framework 1.1. в одном контексте транзакции было невозможно держать открытый OleDbDataReader и параллельно выполнять дополнительные запросы к базе данных или открывать ещё один OleDbDataReader. При попытке выполнить этот трюк мы получали исключение вида:

«There is already an open DataReader associated with this Connection which must be closed first.»

Предыдущий пример работы с BLOB полями как раз и показывает применение технологии MARS. В нем демонстрируется последовательное чтение данных и их одновременное их обновление.

Если обратиться к истории, то мы обнаружим, что технология эта совсем не новая, да и технологией назвать это сложно. Если сравнить вторую версию ADO .Net с первой, то, конечно, разработчики добились определенных успехов. Но возможность использовать несколько RecordSet в одной транзакции была реализована ещё в классическом ADO. Скажу больше, там можно было использовать несколько RecordSet, связанных с одной командой. Это достигалось за счет клонирования команды внутри себя, если обнаруживалось, что уже есть связанное с ней множество. В ADO .NET команда тоже умеет клонировать саму себя. Для этого есть метод Clone(), который необходимо вызывать явно, если вы хотите связать несколько OleDbDataReader с одной командой.

Таким образом, применение MARS возможно не только для MS SQL Server, как пишут во многих источниках информации, но и для других баз данных.

Использование транзакций

Автоматическое управление транзакциями в IBProvider

Любая операция с базой данных должна выполняться в контексте транзакции. В своих примерах я постоянно использовал метод OleDbConnection.BeginTransaction(), так как предпочитаю всегда явно управлять транзакциями.

IBProvider может управлять транзакциями автоматически. Для конфигурирования этого режима существуют несколько свойств инициализации, которые могут быть заданы в строке подключения:

auto_commit — Включает режим автоматического управления транзакциями. По умолчанию: false.
auto_commit_level — Задает уровень изоляции автоматических транзакций.
auto_commit_ddl — Определяет режим выполнения DDL запросов.
auto_commit_ddl_level — Задает уровень изоляции автоматических транзакций для DDL запросов.
СПРАВКА
DDL запросы — запросы на модификацию структуры БД (CREATE/ALTER/DROP). Они позволяют управлять сущностями базы данных: таблицами, триггерами, хранимыми процедурами и т.п.

Следующий пример демонстрирует включение режима автоматического управления транзакциями с уровнем изоляции RepeatableRead:

public void AutoCommitSessionTest()
{
   // Описание: AutoCommit, AutoCommitLevel, автоматическое управление транзакциями, InterBase c#, Firebird ADO Net
   OleDbConnectionStringBuilder builder =
   ConnectionProvider.GetConnectionStringBuilder();
   builder.Add(«auto_commit», true);
   builder.Add(«auto_commit_level», Convert.ToInt32(System.Data.IsolationLevel.RepeatableRead));

   OleDbConnection con = new OleDbConnection(builder.ToString());
   con.Open();

   OleDbCommand cmd = new OleDbCommand(«select count(*) from employee», con);
   Assert.IsTrue((int)cmd.ExecuteScalar() > 0);

   con.Close();
}

Уровни изоляции транзакций в Firebird, InterBase

Уровень изоляции транзакции фактически задает область видимости данных между параллельно выполняющимися транзакциями.

IBProvider поддерживает три уровня изоляции транзакций: Read Committed, Repeatable Read и Snapshot.

Чтобы задать уровень изоляции для транзакции необходимо в метод OleDbConnection.BeginTransaction() передать допустимое значение из перечисления System.Data.IsolationLevel. Если уровень изоляции транзакции не задан, то ADO .Net по умолчанию будет использовать ReadComitted.

Вложенные транзакции в Firebird, InterBase

Одной из замечательных возможностей, которые поддерживает IBProvider это использование вложенных транзакций. Уровень вложенности транзакций не ограничен. Для того чтобы включить использование вложенных транзакций необходимо установить свойство инициализации nested_trans = true. Приведем пример работы с вложенными транзакциями для InterBase, Firebird c#:

public void InternalTransactionTest()
{
    //Описание: NESTED TRANSACTIONS, Firebird ADO .Net, C#, Firebird .Net провайдер
    OleDbConnectionStringBuilder builder = ConnectionProvider.GetConnectionStringBuilderFromUDL();

    //включить вложенные транзакции
    builder.Add(«nested_trans», true);

    OleDbConnection con1 = new OleDbConnection(builder.ToString());
    con1.Open();

    //основная транзакция
    OleDbTransaction trans = con1.BeginTransaction();

    //добавить новую запись
    OleDbCommand cmd_insert = new OleDbCommand(
    «insert into country (country,currency) values (:country,:currency)», con1, trans);

    cmd_insert.Parameters.AddWithValue(«:country», «Russia»);
    cmd_insert.Parameters.AddWithValue(«:currency», «Ruble»);
    Assert.AreEqual(1, cmd_insert.ExecuteNonQuery());

    //запустить вложенную транзакцию
    OleDbTransaction internal_transaction = trans.Begin();

    //запустить вложенную транзакцию внутри вложенной
    OleDbTransaction internal_transaction2 = internal_transaction.Begin();

    //удалить запись во вложенно транзакции третьего уровня
    OleDbCommand cmd_delete = new OleDbCommand(
        «delete from country where country=?», con1, internal_transaction2);
    cmd_delete.Parameters.AddWithValue(«?», «Russia»);
    Assert.AreEqual(1, cmd_delete.ExecuteNonQuery());

    //откатить вложенную транзакцию третьего уровня
    internal_transaction2.Rollback();

    //проверить что запись не была удалена в тразакции второго уровня
    OleDbCommand cmd_check = new OleDbCommand(
        «select count(*) from country where country=?», con1, internal_transaction);

    cmd_check.Parameters.AddWithValue(«?», «Russia»);
    Assert.AreEqual (1, cmd_check.ExecuteScalar());

    //удалить запись
    cmd_delete.Transaction = internal_transaction;
    Assert.AreEqual(1, cmd_delete.ExecuteNonQuery());

    //зафиксировать изменения
    internal_transaction.Commit();

    //проверить в основной транзакции что запись удалена из БД
    cmd_check.Transaction = trans;
    Assert.AreEqual(0, cmd_check.ExecuteScalar());

    trans.Commit();
    con1.Close();
}

TransactionScope и распределенные транзакции в Net 2.0

В Net Framework 2 появилось новое пространство имен System.Transaction, которое предоставляет поддержку распределенных транзакций. IBProvider поддерживает распределенные транзакции за счет расширения COM+ Microsoft Transaction Server (MTS). Распределенные транзакции позволяют нам преодолеть границы базы данных и выполнять действия с различными БД в контексте одной распределенной транзакции. Таким образом, гарантируется, что данные у всех участников распределенной транзакции будут согласованы.

Для организации распределенных транзакций служит объект TransactionsScope. В случае если он обнаружит, что его в контексте используется несколько подключений, он будет использовать распределенную транзакцию, иначе локальную. Следующий пример иллюстрирует поведение объекта TransactionsScope:

public void TransactionScopeTest()
{
  //TransactionScrope автоматически свяжет локальные транзакции с распределенной
  //В данном контексте будет две локальных транзакции на каждое подключение и одна  
  //распределенная.
  using (TransactionScope scope = new TransactionScope())
  {
     //автоматически будет создана локальная транзакция
     OleDbConnection con1 = ConnectionProvider.CreateConnection();
     con1.Open();

     //insert command
     OleDbCommand cmd_insert = new OleDbCommand(
     «insert into country (country,currency) values (:country,:currency)»,con1);
     cmd_insert.Parameters.AddWithValue(«country», «Russia»);
     cmd_insert.Parameters.AddWithValue(«currency», «Rouble»);
     Assert.AreEqual(1, cmd_insert.ExecuteNonQuery());

     //автоматически будет создана ещё одна локальная транзакция 
     OleDbConnection con2 = ConnectionProvider.CreateConnection();
     con2.Open();

     cmd_insert.Connection = con2;
     cmd_insert.Parameters[«country»].Value = «Latvia»;
     cmd_insert.Parameters[«currency»].Value = «Lat»;
     Assert.AreEqual(1, cmd_insert.ExecuteNonQuery());

     //Фиксация распределенной транзакции   
     //Для всех локальных транзакций будет вызван метод Commit()
     scope.Complete();

     //Если ранее распределенная транзакция не была завершена для всех локальных     
     //транзакций будет вызван метод Rollback при вызове IDispose.Dispose()
  }

  //TransactionScope будет использовать локальную транзакцию
  //т.к. все команды выполняются в одном контексте
  using (TransactionScope scope = new TransactionScope())
  {
      OleDbConnection con1 = ConnectionProvider.CreateConnection();
      con1.Open();

      //проверяем, что в предыдущем контексте были добавлены записи
      OleDbCommand cmd_select = new OleDbCommand(
      «select count(*) from country where country=:country», con1);
      cmd_select.Parameters.Add(«country», OleDbType.BSTR);

      //удаляем записи 
      OleDbCommand cmd_delete = new OleDbCommand(
      «delete from country where country=:country», con1);
      cmd_delete.Parameters.Add(«country», OleDbType.BSTR);

      cmd_select.Parameters[«country»].Value = «Russia»;
      cmd_delete.Parameters[«country»].Value = «Russia»;
      Assert.AreEqual(1, cmd_select.ExecuteScalar());
      Assert.AreEqual(1, cmd_delete.ExecuteNonQuery());

      cmd_select.Parameters[«country»].Value = «Latvia»;
      cmd_delete.Parameters[«country»].Value = «Latvia»;
      Assert.AreEqual(1, cmd_select.ExecuteScalar());
      Assert.AreEqual(1, cmd_delete.ExecuteNonQuery());

      scope.Complete(); //commit   

   } //rollback           
}

Управление транзакциями Firebird, InterBase через SQL

Помимо управления транзакциями через Ole Db интерфейсы, IBProvider осуществляет специальную поддержку SQL — запросов для управления транзакциями: SET TRANSACTION, COMMIT, COMMIT RETAIN, ROLLBACK и для Firebird 2 — ROLLBACK RETAIN. Данный метод позволяет указывать специфические параметры контекста транзакции, которые не стандартизированы в OLE DB, но поддерживаются в Firebird. Следующий пример демонстрирует управление транзакциями через SQL:

public void SQLTransactionTest()
{
    //Описание: SET TRANSACTION READ ONLY, COMMIT RETAIN, Firebird ADO Net, C#
    OleDbConnection con1 = ConnectionProvider.CreateConnection();
    con1.Open();

    OleDbCommand cmd = new OleDbCommand(
    «SET TRANSACTION READ ONLY WAIT ISOLATION LEVEL READ COMMITTED», con1);
    cmd.ExecuteNonQuery();

    cmd.CommandText = «select count(*) from employee»;
    Assert.AreNotEqual(0, cmd.ExecuteScalar());

    //поддтверждение транзакции с последующим использованием её контекста
    cmd.CommandText = «COMMIT RETAIN»;
    cmd.ExecuteNonQuery();

    cmd.CommandText = «select count(*) from employee»;
    Assert.AreNotEqual(0, cmd.ExecuteScalar());

    cmd.CommandText = «COMMIT»;
    cmd.ExecuteNonQuery();

    con1.Close();
}

Использование именованных точек сохранения

IBProvider позволяет использовать именованные точки сохранения внутри транзакций. Для задания новой точки необходимо выполнить SQL запрос:

SAVEPOINT save_point_name

Для того чтобы откатить или зафиксировать транзакцию до определенной точки сохранения необходимо выполнить:

ROLLBACK TO SAVEPOINT save_point_name или COMMIT TO SAVEPOINT save_point_name

В следующем примере определяется одна точка сохранения между двумя командами:

public void SavePointTest()
{
     //Описание: SAVEPOINT, ROLLBACK TO SAVEPOINT, Firebird .Net провайдер, InterBase c#
     OleDbConnection con1 = ConnectionProvider.CreateConnection();
     con1.Open();

     OleDbTransaction transaction = con1.BeginTransaction();

     OleDbCommand cmd_insert = new OleDbCommand(
     «insert into country (country,currency) values (:country,:currency)», con1, transaction);
     cmd_insert.Parameters.AddWithValue(«:country», «Russia»);
     cmd_insert.Parameters.AddWithValue(«:currency», «Ruble»);
     Assert.AreEqual(1, cmd_insert.ExecuteNonQuery());

     new OleDbCommand(«SAVEPOINT AFTER_INSERT_POINT», con1, transaction).ExecuteNonQuery();

     //delete country in using 3-level internal transaction context
     OleDbCommand cmd_delete = new OleDbCommand(
         «delete from country where country=?», con1, transaction);
     cmd_delete.Parameters.AddWithValue(«?», «Russia»);
     Assert.AreEqual(1, cmd_delete.ExecuteNonQuery());

     new OleDbCommand(«ROLLBACK TO SAVEPOINT AFTER_INSERT_POINT», con1, transaction).ExecuteNonQuery();

     //check what record was not deleted from Firebird database
     OleDbCommand cmd_check = new OleDbCommand(
         «select count(*) from country where country=?», con1, transaction);

     cmd_check.Parameters.AddWithValue(«?», «Russia»);
     Assert.AreEqual(1, cmd_check.ExecuteScalar());

     transaction.Rollback();
     con1.Close();
}

Обработка ошибок в Visual Studio

Класс OleDbException. Пример на C#

Для обработки ошибок Ole Db в Ado .Net есть свой класс OleDbException, который в отличие от стандартного класса Exception предоставляет дополнительную информацию:

  • Код ошибки OleDb
  • Коллекцию ошибок OleDbError

В общем случае обработка ошибок OLE DB в c# выглядит следующим образом:

try
{
   OleDbConnection con = new OleDbConnection(«Provider=LCPI.IBProvider;Data Source=Empty;»);
   con.Open();
   con.Close();
}
catch (OleDbException ole_ex)
{
    foreach (OleDbError err in ole_ex.Errors)
    {
         Console.WriteLine(«Message:      « + err.Message);
         Console.WriteLine(«Native Error: « + err.NativeError);
         Console.WriteLine(«Source:       « + err.Source);
         Console.WriteLine(«SQL State   : « + err.SQLState);
    }
}
catch (Exception ex)
{
    Console.WriteLine(ex.ToString());
}

В примере умышленно пропущены обязательные параметры строки подключения User ID и Password. В результате выполнения данного кода будет сгенерировано Ole Db исключение. В коллекции ошибок будет содержаться два объекта OleDbError. На экран будет выведено следующее:

Message:      Неопределено обязательное свойство инициализации «Password».
Native Error: 0
Source:       LCPI.IBProvider.2
SQL State   :
Message:      Неопределено обязательное свойство инициализации «User ID».
Native Error: 0
Source:       LCPI.IBProvider.2
SQL State   :

Событие InfoMessage

Предназначено для обработки предупреждений или информационных сообщений поступающих от Ole Db провайдера.

Свойства объектов Ole Db — OleDbProperties .Net

В предыдущих версиях библиотеки ADO у объектов Connection, Command и Recordset был набор свойств Properties, при помощи, которого можно было устанавливать и считывать свойства соответствующих Ole Db интерфейсов: IDBProperties, ISessionProperties, ICommandProperties, IRowsetIndex. По непонятным мне причинам разработчики ADO .Net исключили чтение/запись свойств напрямую, лишив нас возможности получения расширенной информации об источнике данных, а так же тонкой настройки поведения провайдера.

В результате исследования библиотеки ADO .Net было найдено решение данной проблемы, которое было оформлено в отдельный компонент:

public void ReadPropertiesTest()
{
   // Описание: работа с OleDbProperties в IBProvider, Firebird (InterBase) c#
   OleDbConnection con = ConnectionProvider.CreateConnection();
   con.Open();

   //OleDbConnection properties
   OleDbProperties properties = OleDbProperties.GetPropertiesFor(con);
   Assert.AreNotEqual(0, properties.Count);
   PrintProperties(properties);

   //OleDbCommand properties
   OleDbCommand cmd = new OleDbCommand(«select * from country», con, con.BeginTransaction());

   properties = OleDbProperties.GetPropertiesFor(cmd);
   Assert.AreNotEqual(0, properties.Count);
   PrintProperties(properties);

   //Property from Rowset ole db group can be changed
   properties[«Use Bookmarks»].Value = true;
   Assert.IsTrue(Convert.ToBoolean(properties[«Use Bookmarks»].Value));

   //OleDbDataReader properties
   using (OleDbDataReader rdr = cmd.ExecuteReader())
   {
       properties = OleDbProperties.GetPropertiesFor(rdr);
       PrintProperties(properties);
   }

   con.Close();
}

private void PrintProperties(OleDbProperties properties)
{
   foreach (OleDbProperty  prop in properties.Values)
            Console.WriteLine((prop.Required ==true ? «[r] « : «») +
                               prop.Name + «=» + prop.ValueString);
}

Описание компонента для управления свойствами OleDbProperties .Net

Решение было оформлено в виде компонента, который поставляется в виде исходных текстов вместе с примерами к статье. Он реализован в виде словаря OleDbProperties индексированного по названию свойства, в котором присуствует фабричный метод GetPropertiesFor():

Компонент OleDbProperties .Net

Фабричный метод обращается к поставщикам свойств унаследованных от PropertyProviderBase. В компоненте реализовано три поставщика для объектов OleDbCommand, OleDbConnection и OleDbDataReader:

Диаграмма классов OleDbProperties .Net

Каждый из поставщиков возвращает свой набор свойств, соответствующий группам Ole Db:

  • поставщик для OleDbConnection возвращает свойства из наборов Data Source Information, Data Source и Initialization
  • поставщик для OleDbCommand и поставщик для OleDbDataReader возвращают свойства из группы Rowset с тем различием, что для команды можно установить свойства, а для OleDbDataReader-а их можно только читать.
  • Полный список свойств группы Data Source Information
  • Список свойств группы Initialization (задаются в строке подключения к Firebird, InterBase.
  • Свойства набора рядов группы Rowset

Пул подключений

Пул подключений позволяет более эффективно управлять таким ресурсом как соединение с базой данных. Когда после завершения очередной операции с базой данных вы вызываете метод OleDbConnection.Close() соединение с базой отправляется в пул и остается открытым там ещё некоторое время. По умолчанию это время — 60 секунд. Когда клиент инициирует новое соединение с базой, провайдер сначала запрашивает его из пула и только потом, если такого не нашлось, создает новый ресурс. Подходит соединение или нет, определяется по точному соответствию свойства ConnectionString с учетом регистра.

Настройка пула подключений осуществляется при помощи параметра строки подключения «Ole Db Services». Значение параметра — это битовая комбинация следующих флагов:

Флаг Знач. OLE DB сервисы
DBPROPVAL_OS_ENABLEALL -1 Используются все сервисы
DBPROPVAL_OS_RESOURCEPOOLING 1 Ресурсы должны помещаться в пул
DBPROPVAL_OS_TXNENLISTMENT 2 При необходимости сессии должны быть автоматически подключены к глобальной транзакции
DBPROPVAL_OS_AGR_AFTERSESSION 8 Поддержка операций за пределами сессии
DBPROPVAL_OS_CLIENTCURSOR 4 Поддержка клиентских курсоров на уровне OLE DB Services, если их не поддерживает управляемый провайдер
DBPROPVAL_OS_DISABLEALL 0 Все сервисы отключены

В примерах к статье класс OleDbServicesValues содержит константы для всех этих флагов. Для комбинации флагов можно использовать операцию побитового исключения (& ~) констант невостребованных сервисов из константы DBPROPVAL_OS_ENABLEALL.

Следующий пример тестирует производительность при использовании различных Ole Db сервисов:

public void OleDbServicesTest()
{
    const int connection_count = 50;

    //хранит результат работы по всем операциям
    Dictionary<string, double> timeResults = new Dictionary<string, double>();

    OleDbConnectionStringBuilder builder =
    ConnectionProvider.GetConnectionStringBuilderFromUDL();

    //OLE DB SERVISES = Все сервисы включены
    builder.OleDbServices = OleDbServicesValues.EnableAll;
    timeResults.Add(
    String.Format(«OLE DB Services=EnableAll ({0})», builder.OleDbServices),
    DoConnections(builder, connection_count));

    //OLE DB SERVISES = все сервисы отключены         
    builder.OleDbServices = OleDbServicesValues.DisableAll;
    timeResults.Add(
    String.Format(«OLE DB Services=DisableAll ({0})», builder.OleDbServices),
 	    DoConnections(builder, connection_count));

    //OLE DB SERVICES = все включено, за исключением клиентских курсоров
    //и работы за пределами сессии            
    builder.OleDbServices = (OleDbServicesValues.EnableAll &
                           ~ OleDbServicesValues.ClientCursor &
                           ~ OleDbServicesValues.AggregationAfterSession);
    timeResults.Add(
    String.Format(«OLE DB Services=\n»+
                  «\tEnableAll & \n» +
                  «\t~ClientCursor & \n» +
                  «\t~AggregationAfterSession ({0})», builder.OleDbServices),
          DoConnections(builder, connection_count));

    foreach (string key in timeResults.Keys)
          Console.WriteLine(key + «. Seconds elapsed: « + timeResults[key]);
}

/// <summary>
/// Открывает и закрывает много подключений, а так же стартует транзакции
/// </summary>
/// <param name=»builder»></param>
/// <param name=»cnt_connection»></param>
/// <returns>Сколько секунд выполнялось</returns> 
private double DoConnections(OleDbConnectionStringBuilder builder,
     int cnt_connection)
{
    DateTime startTime = DateTime.Now;

    for (int i = 1; i <= cnt_connection; i++)
    {
        OleDbConnection con = new OleDbConnection(builder.ToString());
        con.Open();
        OleDbTransaction trans = con.BeginTransaction();
        trans.Commit();
        con.Close();
    }

    return DateTime.Now.Subtract(startTime).TotalSeconds;
}

Наиболее производительным будет вариант при использовании только пула ресурсов и автоматического подключения транзакций — это соответствует битовой маске OleDbServicesValues.EnableAll & ~OleDbServicesValues.ClientCursor &~ OleDbServicesValues.AggregationAfterSession (параметр “OLE DB Services =-13”).

Чуть медленнее будет работать при использовании всех Ole Db сервисов. И наконец производительность значительно падает (примерно в 20 раз) при полностью выключенных сервисах.

Подробнее об управлении пулом подключений в MSDN.

Отсоединенная модель. DataSet

Заполнение объекта DataSet

Класс DataSet служит для хранения данных, загруженных из базы, в памяти. Фактически он представляет собой набор таблиц связанных отношениями и в идеальном случае копирует структуру исходной базы данных.

Он позволяет существенно сократить количество обращений к базе данных. Это особенно критично для WEB-приложений, для которых частое подключение к базе данных не является оптимальным.

Существуют несколько способов заполнения объекта DataSet:

Первый способ появился ещё в Net Framework 1.0 — это использование класса OleDbDataAdapter:

public void FillDataSetFromDataAdapter()
{
   // Описание: Заполнение DataSet через OleDbDataAdapter, работа с Firebird, InterBase
   DataSet ds = new DataSet();

   using (System.Transactions.TransactionScope scope =
 		new System.Transactions.TransactionScope())
   {
      OleDbConnection con = ConnectionProvider.CreateConnection();
      con.Open();

      OleDbDataAdapter adapter =
           new OleDbDataAdapter(«select * from EMPLOYEE», con);
      adapter.Fill(ds);

      Assert.IsTrue(ds.Tables[0].Rows.Count > 0);
      scope.Complete();
   }
}

Второй способ появился только в ADO .Net 2.0 — это возможность заполнения DataSet используя OleDbDataReader:

public void FillDataSetFromDBReaderTest()
{
    // Описание: Заполнение DataSet через OleDbDataReader, ADO .Net компоненты Firebird и InterBase
    OleDbConnection con = ConnectionProvider.CreateConnection();
    con.Open();
    OleDbCommand cmd =
      new OleDbCommand(«select * from EMPLOYEE»,con,   con.BeginTransaction());

    DataSet ds = new DataSet();
    DataTable tbl = ds.Tables.Add(«EMPLOYEE»);

    using (OleDbDataReader reader =
                           cmd.ExecuteReader(CommandBehavior.CloseConnection))
    {
          ds.Load(reader, LoadOption.OverwriteChanges, tbl);
    }
}

DataTableReader

Данный класс позволяет использовать объект DataTable в режиме однонаправленного ForwardOnly чтения. Он, так же как и OleDbDataReader наследуется от базового класса DBDataReader.

Этот способ чтения таблиц DataSet может быть полезен, когда используются общие методы для отсоединенного источника данных и данных, которые формируются в подсоединенном режиме (OleDbCommand.ExecuteReader()). К примеру, такая ситуация нередкость, когда операции работы с базой данных нужно вынести в веб-сервис. В таком случае, чтобы не переписывать большое количество кода, используещего OleDbDataRead, применяют DataTableReader к данным DataSet.

Следующий пример демонстрирует использование общего метода PrintDBDataReader() для подсоединенного и отсоединенного режимов работы:

public void GetDBReaderFromDataTable()
{
   // Описание: DataTableReader — последовательное, однонаправленное чтение
   //виртуальной таблицы, ADO Net компоненты InterBase (Firebird)
   DataSet ds = new DataSet();
   DataTable tbl = ds.Tables.Add(«EMPLOYEE»);

   //загрузка данных в dataSet 
   OleDbConnection con = ConnectionProvider.CreateConnection();
   con.Open();
   OleDbTransaction trans = con.BeginTransaction();

   ds.Load(new OleDbCommand(
           «select * from EMPLOYEE»,con,trans).ExecuteReader(),
           LoadOption.OverwriteChanges, tbl);

   //используем DataTable в режиме однонаправленного чтения
   this.PrintDBDataReader(new DataTableReader(tbl));

   //OleDbDataReader и подсоединенный режим
   this.PrintDBDataReader(
        new OleDbCommand(«select * from EMPLOYEE», con,  trans).ExecuteReader());

   trans.Commit();
   con.Close();
}

/// <summary>
/// Выводит в консоль данные из DBDataReader
/// </summary>
/// <param name=»reader»></param>
public void PrintDBDataReader(System.Data.Common.DbDataReader reader)
{
     while (reader.Read())
     {
        Console.WriteLine(«*********************************»);
        for (int i = 0; i < reader.FieldCount; i++)
           Console.WriteLine(reader.GetName(i) + «=» + reader[i].ToString());
     }

     reader.Close();
}

Передача изменений обратно в базу данных

После того как в DataSet были внесены изменения, их необходимо передать обратно в базу. Для этого у объекта OleDbDataAdapter есть метод Update(). Прежде чем его использовать, необходимо настроить наш адаптер. В этом нам поможет класс OleDbCommandBuilder. Он позволяет сгенерировать команды для операций вставки, обновления и удаления, а так же создать соответствующую коллекцию параметров команд.

Ниже приведен пример передачи изменений из DataSet в базу данных:

public void UpdateDataSet()
{
   // Описание: OleDbDataAdapter.Update(DataTable) — передача изменений в СУБД Firebird, InterBase c#
   DataSet ds = new DataSet();
   DataTable tbl = ds.Tables.Add(«EMPLOYEE»);

   OleDbConnection con = ConnectionProvider.CreateConnection();
   con.Open();
   OleDbDataAdapter adapter = new OleDbDataAdapter(«select * from EMPLOYEE», con);
   adapter.SelectCommand.Transaction = con.BeginTransaction();
   adapter.Fill(tbl);

   //вносим изменения в DataSet
   foreach (DataRow row in tbl.Rows)
      row[«FIRST_NAME»] = row[«FIRST_NAME»].ToString().ToUpper();

   //генерируем команды для операций update, insert и delete 
   OleDbCommandBuilder cmd_builder = new OleDbCommandBuilder(adapter);
   adapter.DeleteCommand = cmd_builder.GetDeleteCommand();
   adapter.UpdateCommand = cmd_builder.GetUpdateCommand();
   adapter.InsertCommand = cmd_builder.GetInsertCommand();

   //обновление данных
   adapter.Update(tbl);

   //откат сделанных изменений
   adapter.SelectCommand.Transaction.Rollback();
   con.Close();
}

Дата публикации: 25.09.2006. Права на материал принадлежат: IBProvider. При перепечатке ссылка на сайт http://www.ibprovider.com/rus обязательна.