Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
VPS в 21 локации

От 104 рублей в месяц

Безлимитный трафик. Защита от ДДоС.

🔥 VPS до 5.7 ГГц под любые задачи с AntiDDoS в 7 локациях

💸 Гифткод CITFORUM (250р на баланс) и попробуйте уже сейчас!

🛒 Скидка 15% на первый платеж (в течение 24ч)

Скидка до 20% на услуги дата-центра. Аренда серверной стойки. Colocation от 1U!

Миграция в облако #SotelCloud. Виртуальный сервер в облаке. Выбрать конфигурацию на сайте!

Виртуальная АТС для вашего бизнеса. Приветственные бонусы для новых клиентов!

Виртуальные VPS серверы в РФ и ЕС

Dedicated серверы в РФ и ЕС

По промокоду CITFORUM скидка 30% на заказ VPS\VDS

2006 г.

Руководство по работе с БД Firebird с использованием библиотеки ADO .Net 2.0

Меркулов Андрей Александрович, http://www.ibprovider.com/

НазадСодержаниеВперёд

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

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

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

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

auto_commit
Допустимые значения: true, false
Значение по умолчанию: false
Включает режим автоматического управления транзакциями. По умолчанию false

auto_commit_level
Допустимые значения: Read Comitted, Repeatable Read, Snapshot
Значение по умолчанию: Repeatable Read
Задает уровень изоляции автоматических транзакций. По умолчанию Repeatable Read. В ADO .Net уровни изоляции транзакций определены в перечислении IsolationLevel

auto_commit_ddl
Значение по умолчанию: 0
Допустимые значения:
0 - Поддержка DDL запросов отключена
1- Выполнять DDL запрос в выделенной транзакции. Игнорируется если установлено свойство auto_commit
2- Выполнять CommitRetaining после DDL запроса. Игнорируется если установлено свойство auto_commit
Определяет режим выполнения DDL запросов

auto_commit_ddl_level
Допустимые значения: Read Comitted, Repeatable Read, Snapshot
Значение по умолчанию: Read Commited
Задает уровень изоляции автоматических транзакций для DDL запросов.

ПРИМЕЧАНИЕ   DDL запросы (CREATE/ALTER/DROP) позволяют управлять сущностями базы данных: таблицами, триггерами, хранимые процедуры и т.п.

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

  public void AutoCommitSessionTest()
  {
 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();              
  }
Уровни изоляции транзакций

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

IBProvider поддерживает три уровня изоляции транзакций: Read Committed, Repeatable Read и Snapshot. Чтобы задать уровень изоляции для транзакции, необходимо в метод OleDbConnection.BeginTransaction() передать допустимое значение из перечисления System.Data.IsolationLevel.

Вложенные транзакции

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

public void InternalTransactionTest()
{
    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           
}
Управление транзакциями через SQL

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

public void SQLTransactionTest()
{
    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()
{
     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 but existing in 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();
}

Обработка ошибок

Класс OleDbException

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

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

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

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

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

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

   public void ReadPropertiesTest()
   {
      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 индексированного по названию свойства, в котором присуствует фабричный метод GetPropertiesFor() (термин из [2]):

classDiagramOleDbProperties.gif

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

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

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

Полный список свойств из группы Data Source Information, поддерживаемых IBProvider-ом доступен по этому адресу

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

Свойства набора рядов из группы 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 - здесь.


НазадСодержаниеВперёд
VPS/VDS серверы. 30 локаций на выбор

Серверы VPS/VDS с большим диском

Хорошие условия для реселлеров

4VPS.SU - VPS в 17-ти странах

2Gbit/s безлимит

Современное железо!

Бесплатный конструктор сайтов и Landing Page

Хостинг с DDoS защитой от 2.5$ + Бесплатный SSL и Домен

SSD VPS в Нидерландах под различные задачи от 2.6$

✅ Дешевый VPS-хостинг на AMD EPYC: 1vCore, 3GB DDR4, 15GB NVMe всего за €3,50!

🔥 Anti-DDoS защита 12 Тбит/с!

Новости мира IT:

Архив новостей

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 495 7861149
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2019 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...