Дата публикации: 15.11.2013

Отмена выполнения запроса. Проверка реализации. (C#)

В представленном примере проверяется корректность реализации отмены запросов к базе данных Firebird. Проверяется два провайдера:

  • ADO.NET Provider for FireBird
  • LCPI ADO.NET Provider for OLE DB
Для выполнения примера нужно установить оба провайдера и зарегистрировать их фабрики класса. В случае «LCPI ADO.NET Provider for OLE DB» в инсталляторе нужно выбрать провайдеры в выпадающих списках «Defaults For .NET Runtimes».

Сценарий:

  1. Создается два объекта команды
  2. Запускается запрос через первую команду
  3. Вызывается метод Cancel у второй команды

В случае корректной реализации (LCPI ADO.NET Provider for OLE DB) выполнение запроса первой команды не прерывается. В случае некорректной реализации — выполнения запроса будет прервано.

Причина некорректного поведения «ADO.NET Provider for FireBird» объясняется тем, что у Firebird нет возможности отменять работу конкретного запроса. Есть только отмена текущей операции в рамках подключения. Так что, вся нагрузка по отслеживанию текущего выполняемого запроса ложится на плечи компонент доступа. В «ADO.NET Provider for FireBird» этой поддержки нет, поэтому вызов Cancel прерывает работу другого запроса.

Следует отметить, что хотя в «LCPI ADO.NET Provider for OLE DB» и есть специфический код, необходимый для вызова Cancel в другом потоке, но он связан с управлением временем жизни .NET объектами. Собственно сам код, реализующий корректную отмену выполнения запросов, реализован в OLE DB провайдере (IBProvider).

Дополнительно отметим, что в случае подключения к серверу через fbclient.dll, отсутствует гарантия отправки команды отмены операции на сервер. Это ограничения ISC API. Необходимые гарантии обеспечиваются при подключении к Firebird через встроенного клиента — для этого нужно указать в строке подключения «dbclient_type=fb.direct».

////////////////////////////////////////////////////////////////////////////////
//author: Kovalenko Dmitry. IBProvider Team.
//date  : 2013-11-15
using System;
using System.Data;
using System.Data.Common;

using System.Threading;

namespace Sample_0016{
////////////////////////////////////////////////////////////////////////////////
//NOTE.
// Please, install all providers into GAC and register they in "machine.config".
//
////////////////////////////////////////////////////////////////////////////////
//class ThreadWorker

class ThreadWorker
{
 private readonly DbCommand m_cmd;

 public Exception m_exc=null;

 //-----------------------------------------------------------------------
 public ThreadWorker(DbCommand cmd)
 {
  m_cmd=cmd;
 }//ThreadWorker

 //-----------------------------------------------------------------------
 public void ExecuteNonQuery()
 {
  Console.WriteLine("Enter to ThreadWorker.ExecuteNonQuery");

  try
  {
   m_cmd.ExecuteNonQuery();
  }
  catch(Exception e)
  {
   Console.WriteLine("Catch exception in ThreadWorker.ExecuteNonQuery");

   m_exc=e;
  }//catch

  Console.WriteLine("Exit from ThreadWorker.ExecuteNonQuery");
 }//ExecuteNonQuery
};//class ThreadWorker

/////////////////////////////////////////////////////////////////////////////////
//class ConnectionData

class ConnectionData
{
 public string dbfactory;
 public string cn_str;
};//class ConnectionData

/////////////////////////////////////////////////////////////////////////////////
//class Program

class Program
{
 //-----------------------------------------------------------------------
 //LCPI .NET Provider for OleDb.

 private static readonly ConnectionData sm_cn_data__oledb
  =new ConnectionData
   {
    dbfactory="lcpi.data.oledb",
    cn_str="provider=LCPI.IBProvider.3;"
          +"location=localhost:d:\\database\\ibp_test_fb25_d3.gdb;"
          +"user id=gamer;"
          +"password=vermut;"
          +"dbclient_library=fbclient.dll"
   };//sm_cn_data__oledb

 //-----------------------------------------------------------------------
 //Original .Net Provider for FireBird. Welcome to world of industrial programming.

 private static readonly ConnectionData sm_cn_data__fb_net_client
  =new ConnectionData
   {
    dbfactory="FirebirdSql.Data.FirebirdClient",
    cn_str="DataSource=localhost;"
          +"Database=d:\\database\\ibp_test_fb25_d3.gdb;"
          +"user=gamer;"
          +"password=vermut;"
   };//sm_cn_data__fb_net_client

 //-----------------------------------------------------------------------
 //change this data for switch between providers

 private static readonly ConnectionData sm_cn_data
  =sm_cn_data__oledb;

 //-----------------------------------------------------------------------
 static int Main()
 {
  int resultCode=0;

  try
  {
   var dbfactory=DbProviderFactories.GetFactory(sm_cn_data.dbfactory);

   using(var cn=dbfactory.CreateConnection())
   {
    {
     var cn_assembly_name=cn.GetType().Assembly.GetName();

     Console.WriteLine("Connection Assembly: {0}, {1}",
                        cn_assembly_name.Name,
                        cn_assembly_name.Version);
    }//local

    cn.ConnectionString=sm_cn_data.cn_str;
    cn.Open();

    using(var tr=cn.BeginTransaction(IsolationLevel.RepeatableRead))
    {
     using(DbCommand cmd1=cn.CreateCommand())
     using(DbCommand cmd2=cn.CreateCommand())
     {
      cmd1.Transaction=tr;
      cmd1.CommandText="execute procedure SP_EXEC_DUMMY_COUNTER(100000000)";

      var threadWorker=new ThreadWorker(cmd1);

      cmd2.Transaction=tr;
      cmd2.CommandText="select * from RDB$DATABASE";

      cmd2.Prepare();

      Thread thread=new Thread(threadWorker.ExecuteNonQuery);

      try
      {
       thread.Start();

       while(thread.IsAlive)
       {
        Thread.Sleep(2000);

        Console.WriteLine("Cancel");

        cmd2.Cancel();
       }//while

       Console.WriteLine("threadWorker was stopped");

       if(Object.ReferenceEquals(threadWorker.m_exc,null))
       {
        Console.WriteLine("No exception");
       }
       else
       {
        Console.WriteLine("Thread exception: {0} - {1}",
                          threadWorker.m_exc.Source,
                          threadWorker.m_exc.Message);
       }//else
      }
      finally
      {
       thread.Join();
      }//finally
     }//using cmd1,cmd2
    }//using tr
   }//using cn
  }
  catch(Exception e)
  {
   resultCode=1;

   Console.WriteLine("");
   Console.WriteLine("ERROR: {0} - {1}",e.Source,e.Message);
  }//catch

  return resultCode;
 }//Main
};//class Program

////////////////////////////////////////////////////////////////////////////////
}//namespace Sample_0016


Output of sample [used IBProvider v3].
Вывод. Использовались «lcpi.oledb.net» и «LCPI.IBProvider.3».


Output of sample [used Firebird .Net Client 3.2].
Вывод. Использовался «FirebirdSql.Data.FirebirdClient» v3.2.