Отмена выполнения запроса. Проверка реализации. (C#)
В представленном примере проверяется корректность реализации отмены запросов к базе данных Firebird. Проверяется два провайдера:
- ADO.NET Provider for FireBird
- LCPI ADO.NET Provider for OLE DB
Сценарий:
- Создается два объекта команды
- Запускается запрос через первую команду
- Вызывается метод 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
Вывод. Использовались «lcpi.oledb.net» и «LCPI.IBProvider.3».
Вывод. Использовался «FirebirdSql.Data.FirebirdClient» v3.2.
