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

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

Любая операция с базой данных должна выполняться в контексте транзакции. В своих примерах я постоянно использовал метод 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 позволяет использовать именованные точки сохранения внутри транзакций. Для задания новой точки необходимо выполнить 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 :

    Компонент для управления свойствами

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

    Фабричный метод обращается к поставщикам свойств, унаследованных от 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 timeResults = new Dictionary();
    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]); }

    ///

    /// Открывает и закрывает много подключений, а так же стартует транзакции

    ///


    ///

    ///

    /// Сколько секунд выполнялось 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 - здесь.


    Событие 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);
    }

    появилось новое пространство имен

    В 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 поддерживает три уровня изоляции транзакций: 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(); }

    BindingSource

    С выходом Net 2.0 технология Data Binding получила свое дальнейшее развитие. Появился новый класс BindingSource. Он является прокси-объектом между поставщиками данных и элементами управления, отображающими эти данные. Теперь элемент управления привязывается не к объектам, поставляющим данные (DataTable, DataSet, DataView), а к объекту BindingSource. Это позволяет использовать привязку ещё незагруженных данных, а также синхронизировать данные в случае использования общего BindingSource для нескольких элементов управления.
    Продолжая построение нашего приложения, приведу, на мой взгляд, яркий пример, который демонстрирует все преимущества объекта BindingSource.
    Добавим на форму новый элемент управления DataGridView. Давайте попробуем отобразить в нем список проектов, у которых текущий служащий, выбранный в ComboBox-e, был руководителем. Известно, что таблицы PROJECT и EMPLOYEE связаны между собой через поле TEAM_LEADER по внешнему ключу INTEG_36. Воспользуемся уже знакомым механизмом связи элемента управления и источника данных:
    BindingSource

    Необходимо найти в списке возможных источников данных уже использованный мной ранее BindingSource для таблицы служащих EMPLOYEE и запросить дизайнер создать новый BindingSource для связи между проектами и служащими по внешнему ключу INTEG_36:
    Добавим элемент TextBox для отображения описания проекта, которое хранится в BLOB поле PROJECT_DESCR таблицы PROJECT. Для того, чтобы связать его с текущим проектом, в списке DataGridView необходимо установить Binding для свойства Text:
    BindingSource


    DataTableReader

    Данный класс позволяет использовать объект DataTable в режиме однонаправленного ForwardOnly чтения. Он, так же как и OleDbDataReader, наследуется от базового класса DBDataReader. Этот способ чтения таблиц DataSet может быть полезен, когда используются общие методы для отсоединенного источника данных и данных, которые формируются методом OleDbCommand.ExecuteReader() в подсоединенном режиме. Следующий пример демонстрирует использование общего метода PrintDBDataReader() для подсоединенного и отсоединенного режимов работы:
    public void GetDBReaderFromDataTable() { 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(); }
    ///
    /// Выводит в консоль данные из DBDataReader
    ///

    ///
    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(); }

    DDL запросы. CREATE/ALTER/DROP

    Данный вид запросов позволяет управлять метаданными БД. Вы можете создавать, удалять и модифицировать колонки, таблицы и целые базы данных через SQL выражения, в тексте которых содержатся DDL-инструкции. Если применять их совместно со схемами метаданных, то можно без особых усилий копировать структуры существующих баз данных и создавать новые.
    Все DDL запросы, за исключением CREATE DATABASE и DROP DATABASE, могут выполняться как в режиме автоматического подтверждения, так и в контексте транзакции. По умолчанию фиксирование изменений произведенных DDL запросами отключено. Это сделано из соображений безопасности. Для того, чтобы включить подтверждение DDL запросов, необходимо установить свойство auto_commit_ddl. Его описание есть в разделе «Методы подключения к базе данных» этой статьи.
    Приведу пример использования DROP DATABASE для удаления базы данных:
    private void DropDatabase() { if (File.Exists(databasePath)) { OleDbConnectionStringBuilder builder = CreateConnectionStringBuilderForSample();
    //отключаем использование пула для этого подключения
    builder.OleDbServices = OleDbServicesValues.EnableAll & ~OleDbServicesValues.ResourcePooling;
    OleDbConnection con = new OleDbConnection(builder.ToString()); con.Open();
    new OleDbCommand("drop database",con).ExecuteNonQuery(); con.Close(); } }
    Обратите внимание на то, что для подключения, которое будет использоваться для удаления базы данных, мы отключили использование пула ресурсов. IBProvider умеет информировать сервисы Ole Db о ставших недоступными подключениях и в данном случае это действие является избыточным. Но оно оставлено здесь для решения возможных проблем при использовании Ole Db провайдеров других производителей.
    Теперь DDL для создания новой базы данных:
    private OleDbConnection CreateDatabase() { //подключение к существующей бд employee.gdb
    OleDbConnection con = ConnectionProvider.CreateConnection(); con.Open();
    //создание новой базы данных
    new OleDbCommand( "create database '" + server_name + ":" + databasePath + "'\n" + "USER '" + user_name + "' \n" + "PASSWORD '" + password + "' \n", con).ExecuteNonQuery();

    con.Close();

    return new OleDbConnection( CreateConnectionStringBuilderForSample().ToString()); }

    И, наконец, законченный пример, который сначала удаляет базу данных, потом создает на её месте новую и определяет в ней две таблицы, связанные внешним ключом:

    public void CreateNewDBSample() { DropDatabase();

    OleDbConnection con = CreateDatabase(); con.Open(); OleDbTransaction trans = con.BeginTransaction();

    //создаем таблицу SAMPLE_TABLE с двумя колонками

    ExecuteDDL( "CREATE TABLE SAMPLE_TABLE( " + //int column " ID INTEGER NOT NULL, " + //varchar column

    " NAME VARCHAR(64), " + //primary key

    "CONSTRAINT PK_SAMPLE_TABLE PRIMARY KEY(ID) )", trans);

    //создаем SAMPLE_TABLE_2 связанную через FOREIGN KEY ExecuteDDL( "CREATE TABLE SAMPLE_TABLE_2 ( " + " ID INTEGER NOT NULL, " + //int columns

    " PARENT INTEGER NOT NULL, " + //int column

    "CONSTRAINT PK_SAMPLE_TABLE_2 PRIMARY KEY(ID), " + //primary key

    "CONSTRAINT FK_SAMPLE_TABLE_PARENT " + //foreign key

    "FOREIGN KEY(PARENT) REFERENCES SAMPLE_TABLE(ID))", trans);

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

    Добавление логики управления данными

    Мы разобрались как отображать, связывать и редактировать данные. Теперь давайте завершим наш пример и научим приложение записывать сделанные нами изменения обратно в базу данных.
    У нас единственным редактируемым полем является описание текущего проекта. Для записи изменений, сделанных в нем, мы воспользуемся методом TableAdapter.Update() для таблицы PROJECT. Добавим элемент Button на нашу форму и в обработчике cсобытия Click напишем следующий код:
    private void btnSaveChanges_Click(object sender, EventArgs e) { try { this.pROJECTTableAdapter.Update(this.jobDataSet.PROJECT); MessageBox.Show("Save was successful"); } catch (Exception exception) { MessageBox.Show(exception.Message); } }
    Так же неплохо было бы иметь возможность откатить сделанные изменения. Добавим ещё одну кнопку и в обработчике события Click поместим код, который будет отменять все изменения в DataSet, произведенные с момента последнего сохранения:
    private void btnUndoChanges_Click(object sender, EventArgs e) { this.jobDataSet.RejectChanges(); //требуется для обновления содержимого TextBox
    this.iNTEG36BindingSource.CurrencyManager.Refresh(); }
    В процессе написания кода передачи изменений в БД, я столкнулся со следующей проблемой: при редактировании связанных данных через TextBox изменения не передавались в DataSet и метод DataSet.HasChanges() всегда возвращал false. Для решения этой проблемы необходимо в обработчик события TextBox.Validate добавить следующий код:
    private void textBox1_Validated(object sender, EventArgs e) { this.iNTEG36BindingSource.EndEdit(); }
    Завершенное приложение JobManager доступено в архиве с примерами к статье.

    Другие изменения

    Новое свойство источника данных «IB Database creation date» позволяет узнать дату создания базы данных. Прочитать его значение можно при помощи класса OleDbProperties, который подробно рассмотрен в разделе «Свойства объектов Ole Db»

    Класс TableAdapter

    Он является ключевым звеном в цепочке связи данных с пользовательскими элементами управления. Если провести аналогию с терминами М. Фаулера [], то TableAdapter является шлюзом таблицы данных для DataTable. Он инкапсулирует в себе логику обновления загрузки и поиска данных и относится к Data Access Layer. Что же касается DataSet и DataTable, то их можно отнести к уровню бизнес-логики (Business Layer)
    Visual Studio .Net 2005 сама позаботилась о генерации кода этого класса. Давайте посмотрим, что же она нам предлагает. Итак:
  • методы Fill() и GetData() – единственное отличие в том, что Fill принимает существующую DataTable в качестве аргумента, а GetData() создает новую, заполняет её данными и возвращает клиенту.

  • Свойство ClearBeforeFill, которое используется вышеназванными методами для определения, стоит ли очищать таблицу перед её заполнением.

  • Стандартный набор CRUD операций: insert, update, delete, среди которых присуствуют перегруженные методы с типизированными аргументами, соответствующими таблице базы данных

  • Общее свойство Connection

  • Так же у нас есть возможность создать дополнительные запросы к базе данных. Для этого нам понадобится инструмент Search Criteria Builder. Для его запуска необходимо у нашего адаптера выбрать пункт меню «Add Query»:
    Класс TableAdapter

    Вводим название запроса (а фактически название нового метода в вашем TableAdapte-е), а так же его текст, либо вручную указав условие выборки, либо используя инструмент Query Builder:
    Класс TableAdapter



    ПРИМЕЧАНИЕ.При записи условия выборки используйте позиционные параметры (символ «?»). Код метода будет сгенерирован автоматически, так что особого неудобства это не доставит.

    После всех операций будет сгенерирован соответствующий метод. Так же дизайнер VS .Net 2005 добавит компонент ToolStrip к вашей форме с кнопкой запуска этого метода и полем для задания фильтра. Мне кажется это излишним, но, может быть, кому-нибудь понравится.

    СОВЕТ. Visual Studio .Net 2005 разделяет код, используемый дизайнером и пользовательский код за счет partial классов. В нормальном приложении нам наверняка понадобиться расширить логику TableAdapter. Сделать это можно, описав partial class, соответствующий классу конкретного TableAdapter-a, сгенерированного дизайнером.


    Литература


    1.обратноМ.Фаулер. Архитектура корпоративных программных приложений. Изд. Москва – Спб – Киев. 2004 г.
    2.обратноЭ. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. Приемы объектно-ориентированного проектирования. Паттерны проектирования. Изд. Питер. 2006 г.
    3.А. Ковязин, С.Востриков. Мир InterBase. Архитектура, администрирование и разработка приложений баз данных в InterBase/Firebird/Yaffil (+ CD-ROM). Изд. КУДИЦ-Образ, Питер. 2005 г.
    4.Б.Бошемин. Основы ADO .Net. Изд. Вильямс. 2003 г.


    Примеры к статье



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

    После того, как мы изменили данные в DataSet, их необходимо передать обратно в базу. Для этого у объекта OleDbDataAdapter есть метод Update(). Прежде чем его использовать, необходимо настроить наш адаптер. В этом нам поможет класс OleDbCommandBuilder. Он позволяет сгенерировать команды для операций вставки, обновления и удаления, а так же создать соответствующую коллекцию параметров команд. Ниже приведен пример передачи изменений из DataSet в базу данных:
    public void UpdateDataSet() { 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(); }

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

    Отдельно необходимо остановиться на методах передачи изменений обратно в базу данных. VS .Net 2005 умеет генерировать код запросов для методов insert, update, delete. В некоторых случаях этого может оказаться достаточно, но, как показывает опыт, полученные SQL-выражения пытаются претендовать на универсальность и поэтому не оптимальны, а порой не работоспособны.
    Приведу пример: В поставке с Firebird идет база данных employee.fdb. В ней есть таблица SALES. Прежде всего, обращаю внимание на поле AGED, которое доступно только для чтения, т.к. вычисляется с помощью выражения
    COMPUTED BY (ship_date - order_date)
    Если указать для Select Command текст:
    SELECT * FROM SALES
    то колонка AGED будет добавлена во все команды обновления. При попытке передать изменения в базу, будет сгенерировано исключение. Необходимо вручную отредактировать текст запросов для insert, update, delete команд и убрать из обновления данную колонку.
    Отредактировать SQL выражения можно, вызвав команду «Edit Queries in DataSet designer»:
    Передача изменений в базу данных через TableAdapter

    Откроется дизайнер DataSet, в котором необходимо выбрать нужный TableAdapter (в данном случае это SALESTTableAdapter):
    Передача изменений в базу данных через TableAdapter

    В списке свойств появятся необходимые нам объекты OleDbCommand:
    Передача изменений в базу данных через TableAdapter

    Читатель может подумать, что данный случай - исключение, но это не так. Если вы выберете такой способ создания слоя доступа к данным (Data Access Layer), то будьте готовы постоянно вмешиваться в автоматический процесс генерации SQL запросов.

    Встроенные инструменты Visual Studio 2005

    Встроенные инструменты Visual Studio 2005 могут оказаться незаменимым подспорьем при написании приложений баз данных. Давайте разберём по шагам создание примера простого приложения JobManager.

    Полезные ссылки

  • Список Ole Db провайдеров различных производителей

  • Описание Ole Db схем в MSDN

  • Управляющие последовательности ODBC

  • Firebird 2 release notes

  • Firebird SQL Server

    Схемы метаданных БД.

    Неотъемлемой частью всех Ole Db провайдеров являются схемы метаданных. Они используются клиентами для получения описания базы данных: списка хранимых процедур, структур таблиц, зарегистрированных доменов, ограничений, первичных и внешних ключей и т.д. Для того, чтобы Ole Db провайдер смог работать с библиотекой ADO .Net, он должен поддерживать Ole Db схемы, так как компоненты библиотеки активно используют эту информацию. По этой ссылке расположен список схем, которые поддерживает IBProvider
    Запросить определенную схему можно по её названию. Для этого у объекта OleDbConnection есть метод GetSchema(). В Net 2 появился метод GetOleDbSchema(), который в качестве аргумента принимает одно из значений OleDbSchemaGuid. Оба этих метода ведут себя одинаково и возвращают абсолютно идентичные экземпляры DataTable с набором информации по схеме.
    У каждой схемы есть набор колонок, по которым можно отфильтровать возвращаемый результат. Например, в схеме COLUMNS можно наложить ограничения по следующим полям:
    Restriction columns: TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME. Если мы хотим получить описание всех колонок для таблицы EMPLOYEE, мы должны использовать схему COLUMNS следующим образом:
    DataTable schema_table = connection.GetSchema("COLUMNS", new string[] { null, null, "EMPLOYEE" });
    Аналогично для метода GetOleDbSchema():
    DataTable schema_table = connnection.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, new object[] { null, null, "EMPLOYEE" });

    Создание каркаса приложения

    Создайте новую форму JobForm. Добавьте на форму ComboBox и перейдите в редактор ComboBox Tasks:
    Создание каркаса приложения

    Далее необходимо в свойстве Data Source выбрать действие «Add Project Data Source». Откроется мастер создания источников данных:
    Создание каркаса приложения

    Выбираем тип источника Database и идем далее. В списке подключений будет уже созданное ранее в Server Explorer подключение:
    Создание каркаса приложения

    Делаем все, как показано на рисунке и переходим на следующий шаг:
    Создание каркаса приложения

    Мастер предлагает нам сохранить параметры источника данных в файле конфигурации приложения. По умолчанию строка подключения будет сохранена в области «Application Settings» и будет не доступна для редактирования внутри приложения. Это можно изменить, установив для настройки свойство Scope = User. А пока разрешим создание секции в конфигурационном файле и двигаемся далее:
    Создание каркаса приложения

    На завершающем шаге нам предлагают создать DataSet. Выберем для него все доступные таблицы базы данных и жмем кнопку «Finish».
    Теперь в качестве источника данных для нашего ComboBox выбираем таблицу EMPLOYEE. Свойству DisplayMember установим значение FULL_NAME:
    Создание каркаса приложения

    После завершения операции на форму будут добавлены три новых компонента. Это DataSet, TableAdapter и BindingSource. DataSet нам уже знаком, а вот два других объекта появились только в Net 2.0 и будут рассмотрены далее.

    Создание подключения в Server Explorer

    Создание подключения в Server Explorer

    Выберем команду «Add Connection». Откроется диалог с выбором источника данных. В нем выбираем Data Source = и Data provider = .Net Framework Data Provider for OLE DB. Жмем Ok:
    Создание подключения в Server Explorer

    Далее появится уже знакомый нам диалог:
    Создание подключения в Server Explorer

    Выберите из списка OleDb провайдеров – IBProvider третьей версии. И нажмите кнопку Data Links:
    Создание подключения в Server Explorer

    Здесь есть два важных момента: необходимо включить опции «Разрешить автоматические транзакции» и «Разрешить сохранение пароля». Убедимся, что все настроено правильно, нажав на кнопку «Проверить подключение».
    Если мы все сделали правильно, в списке подключений Server Explorer появится новое, для которого будет доступен список объектов базы данных:
    Создание подключения в Server Explorer


    Специальная поддержка DML (Data Manipulation Language)

    Во второй версии Firebird появилось несколько нововведений. Одно из них это инструкция EXECUTE BLOCK. Она позволяет выполнить блок инструкций на стороне сервера, фактически это виртуальная хранимая процедура. Следующий пример демонстрирует использование ресурсов сервера базы данных для выполнения простого арифметического действия:
    public void ExecuteBlockSQLTest() { OleDbConnection con = OpenFB2Connection(); OleDbTransaction trans = con.BeginTransaction();
    //текст команды
    string execute_block_data = "EXECUTE BLOCK (X INTEGER = :X) \n" + "RETURNS (Y INTEGER) \n" + "AS \n" + "BEGIN \n" + " Y = X * 2; \n" + "SUSPEND; \n" + "END \n";
    //входящий параметр
    int in_parameter_X = 2;
    OleDbCommand cmd = new OleDbCommand(execute_block_data, con, trans); cmd.Parameters.AddWithValue("X", in_parameter_X);
    //выполнение команды EXECUTE BLOCK
    Assert.AreEqual((int)cmd.ExecuteScalar(), in_parameter_X * 2); trans.Commit();
    con.Close(); }
    Еще одно новшество, которое подарил нам FB2, это инструкция INSERT RETURNING. Фактически она позволяет выполнить операцию вставки данных и прочитать значения, которые были добавлены в процессе этой операции. Это особенно актуально для получения значения идентификатора новой записи, для которого использовался генератор:
    public void InsertReturning() { OleDbConnection con = OpenFB2Connection(); OleDbTransaction trans = con.BeginTransaction();
    //новая команда INSERT RETURNING OleDbCommand cmd = new OleDbCommand( "insert into customer (cust_no, customer) \n" + "values(GEN_ID(CUST_NO_GEN,1),:customer_name) \n" + "RETURNING cust_no",con,trans);
    cmd.Parameters.AddWithValue("customer_name", "New customer"); //добавляем один выходной параметр
    cmd.Parameters.Add("customer_no", OleDbType.Integer) .Direction =ParameterDirection.Output;
    Assert.AreEqual(1, cmd.ExecuteNonQuery());
    //удаляем запись, используя значение генератора, полученного //через INSERT ..
    RETURNING

    OleDbCommand cmd_delete = new OleDbCommand( "delete from customer where cust_no=?", con, trans);

    cmd_delete.Parameters.AddWithValue("?",cmd.Parameters["customer_no"].Value); Assert.AreEqual(1, cmd_delete.ExecuteNonQuery());

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

    ROLLBACK RETAIN – позволяет откатить транзакцию на момент старта или до последнего вызова COMMIT_RETAIN, оставляя возможность её дальнейшего использования. Давайте рассмотрим это на примере:

    public void RollbackRetainTest() { OleDbConnection con = OpenFB2Connection(); OleDbTransaction trans = con.BeginTransaction();

    //insert new record OleDbCommand cmd = new OleDbCommand( "insert into customer (cust_no, customer) " + "values(GEN_ID(CUST_NO_GEN,1),'new customer')", con, trans); Assert.AreEqual(1, cmd.ExecuteNonQuery());

    //ROLLBACK RETAIN new OleDbCommand("ROLLBACK RETAIN", con, trans).ExecuteNonQuery();

    //transaction will be active and we can execute some command again

    cmd = new OleDbCommand( "select count(*) from customer", con, trans);

    Assert.IsTrue((int)cmd.ExecuteScalar() > 0 );

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

    Ключевое слово ROWS соответствует последним стандартам ANSI SQL и является альтернативой FIRST/SKIP. Оно позволяет указать количество обрабатываемых записей. Может быть использовано в UNION, любых подзапросах, а так же в командах DELETE и UPDATE. Следующий пример читает из базы данных записи с первой по третью:

    public void RowsKeywordTest() { OleDbConnection con = OpenFB2Connection(); OleDbTransaction trans = con.BeginTransaction();

    //command will return 3 records

    OleDbCommand cmd = new OleDbCommand( "select * from customer rows 1 to 3", con, trans);

    short rec_count = 0;

    using (OleDbDataReader reader = cmd.ExecuteReader()) while (reader.Read()) { rec_count++; }

    Assert.AreEqual(3, rec_count);

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

    Я привел примеры только некоторых изменений в DML Firebird 2, которые требовали специальной поддержки со стороны Ole Db провайдера.Для изучения полного списка изменений рекомендую вам обратиться к документу: Firebird 2 release notes. Там действительно есть много того, что может заинтересовать разработчиков. Это производные таблицы, новые функции, улучшенный UNION, инструкция NEXT VALUE FOR, поддержка планов для операций обновления и удаления и много чего другого.

    Управляющие последовательности ODBC

    Управляющие последовательности позволяют преобразовывать текст запроса в процессе выполнения. Последовательность включается в текст запроса в фигурных скобках. Например {fn CURDATE} – выражение будет преобразовано в значение серверного времени.
    В ODBC определены управляющие последовательности для следующих характеристик:
  • Работа со временем и датами

  • Функции преобразования типов

  • Предикат LIKE

  • OUTER JOIN

  • Вызовы хранимых процедур

  • Данное расширение активно используется такими инструментами, как MS SQL Server, Crystal Reports, различные OLAP средства и их поддержка со стороны Ole Db провайдера обеспечивает возможность их совместного использования.
    Для того, чтобы включить поддержку ODBC расширений в IBProvider, необходимо установить свойство инициализации support_odbc_call = true. По умолчанию поддержка отключена.
    IBProvider умеет вызывать хранимые процедуры в ODBC стиле, а также поддерживает следующие функции, которые могут быть использованы внутри управляющих последовательностей:
  • Функции для работы с датами и временем: CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, CURDATE, CURTIME, DAYNAME, DAYOFMONTH, DAYOFWEEK, DAYOFYEAR, EXTRACT, HOUR, MINUTE, MONTH, MONTHNAME, NOW, QUARTER, SECOND, WEEK, YEAR.

  • Конвертирование данных: CONVERT

  • Системные функции: USER

  • Строковые функции: UCASE

  • Следующий пример демонстрирует применение управляющих последовательностей в тексте SQL запросов:
    public void ODBCQueriesTest() { OleDbConnectionStringBuilder builder = ConnectionProvider.GetConnectionStringBuilderFromUDL(); builder.Provider = "LCPI.IBProvider.2"; builder.Add("support_odbc_query","true");
    OleDbConnection con = new OleDbConnection(builder.ToString()); con.Open(); OleDbTransaction trans = con.BeginTransaction();
    //select current day name OleDbCommand cmd = new OleDbCommand( "select " + "{fn dayname({fn now()})} as DAY_NAME," + "{fn dayofweek({fn now()})} as DAY_OF_WEEK," + "{fn dayofmonth({fn now()})} as DAY_OF_MONTH," + "{fn dayofyear({fn now()})} as DAY_OF_YEAR " + "from RDB$DATABASE", con, trans);
    using (OleDbDataReader rdr = cmd.ExecuteReader()) if (rdr.Read()) for (int i = 0; i < rdr.FieldCount; i++) Console.WriteLine(rdr.GetName(i) + ": " + rdr[i].ToString());
    trans.Commit(); con.Close(); }
    На момент написания статьи ODBC запросы поддерживались в 1-й и 2-й версии IBProvider и ещё не были реализованы в третьей. Так что для использования этой возможности необходимо указывать в строке подключения Provider = "LCPI.IBProvider.2"
    За более подробной информацией по использованию управляющих последовательностей ODBC советую вам обратиться к документу «ODBC Programmer’s Reference» и изучить раздел «Escape Characters in ODBC».

    я рассмотрел наиболее часто используемые

    В своем обзоре я рассмотрел наиболее часто используемые возможности библиотеки ADO .Net на примере OLE DB провайдера.
    Основное преимущество OLE DB провайдеров перед управляемыми (.Net Data Providers) – это возможность их использования не только в среде .Net Framework, а практически в любых средах, поддерживающих COM.
    Использование IBProvider в качестве поставщика данных Firebird позволяет:
  • Обмениваться данными с приложениями Microsoft Office (включая Access и Outlook)

  • Использовать VBA (Visual Basic for Applications).

  • Выгружать данные в 1С и обратно.

  • Использовать Firebird в качестве связанного сервера MS SQL.

  • Писать расширенные сценарии автоматизации при помощи WSH (Windows Script Host), VBScript, Java Script

  • Писать WEB-приложения, WEB-сервисы c базами данных не только на ASP.Net, но и на ASP

  • Производить анализ данных при помощи различных OLAP средств за счет поддержки ODBC управляющих последовательностей.

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

  • И ещё много чего.

  • IBProvider поддерживает все существующие версии Interbase/Yaffil/Firebird. Имеет встроенный менеджер управления памятью и SWAP для пользовательских данных. Поддерживает многопоточность, а так же серверные и клиентские курсоры. Осуществляет многоязыковую поддержку.
    Надеюсь, данное руководство позволит вам расширить круг применяемых возможностей ADO .Net не только для работы с Firebird, но и с другими базами данных.

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

    Класс DataSet служит для хранения данных, загруженных из базы, в памяти. Фактически он представляет собой набор таблиц, связанных отношениями и в идеальном случае копирует структуру исходной базы данных.
    Он позволяет существенно сократить количество обращений к базе данных. Это особенно критично для WEB-приложений, для которых частое подключение к базе данных не является оптимальным.
    Существуют несколько способов заполнения объекта DataSet:
    Первый из них, который появился ещё в Net Framework 1.0, это способ заполнения DataSet при помощи класса OleDbDataAdapter:
    public void FillDataSetFromDataAdapter() { 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() { 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); } }

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

    Для доступа к базам данных в ADO (а теперь эта возможность есть и в ADO.Net) используются OLE DB провайдеры. Ole Db Provider представляет собой драйвер для доступа к базе данных при помощи OLE DB интерфейсов. Для взаимодействия с OLE DB провайдером в .Net реализовано пространство имен System.Data.OleDb.
    При работе с Firebird я использую IBProvider (www.ibprovider.com) и в своем повествовании буду опираться, прежде всего, на его функциональность. Разработчики IBProvider поддерживают три коммерческих версии драйвера. Так же есть бесплатная. Для написания примеров применялся IBProvider третьей версии и только для примера управляющих ODBC последовательностей использовался IBProvider v2.
    Список основных различий в версиях IBProvider:


    Возможности
    IBProvider Free
    IBProvider v1/IBProvider v2
    IBProvider v3
    Поддержка всей линейки серверов Interbase, Yaffil, Firebird, включая FB2 да да да
    Возможность работы с ADO и соответственно с Microsoft Office, VBA, VBScript, Java Script и др. да да да
    Отсутствие ограничений на размер получаемых данных да да да
    Поддержка ADO.Net нет да да
    Возможность работы в качестве MS SQL Linked Server нет да да
    Работа с метаданными нет да да
    Поддержка обновляемых множеств нет нет/да пока нет
    Поддержка распределенных транзакций нет да да
    Вложенные транзакции нет нет да
    Поддержка управляющих последовательностей ODBC для SQL запросов (используется в Crystal Reports, MS SQL Server, различные OLAP и др. инструменты) нет да пока нет
    Поддержка DDL запросов да, но без поддержки SQL парсером провайдера да да
    Тип закладок 4 байта 4 байта/8 байт 8 байт
    Поддержка Client Cursor Engine нет да да
    Уведомления о завершении транзакции нет нет да
    Предоставление расширенной информации о сервере (версия, тип сервера, размер страницы). нет да да
    Возможность задавать используемую клиентскую библиотеку (gds32.dll или fbclient.dll) нет нет да
    Поддержка изменений в DML (Data Model Language) для Firebird 2 нет нет да
    Новые свойства и алгоритмы Firebird 2 (возможность узнать дату создания базы и т.п.) нет нет да

    Наиболее современным решением является IBProvider третьей версии. В его основе лежит абсолютно новое ядро и в нем реализованы технологии управления данными, которые явились результатом 5-тилетних исследований в данной области, а так же вобрали в себя опыт разработки крупных программных проектов и библиотек доступа к данным. На момент написания статьи в третьей версии не была реализована технология обновляемых множеств, а так же не поддерживались управляющие последовательности ODBC. Но, насколько мне известно, поддержка ODBC Escape Sequences уже планируется разработчиками в ближайших версиях провайдера.

    ExecuteNonQuery

    Метод применяется для выполнения запросов, которые возвращают количество обработанных записей, таких как insert, update, delete, а так же для выполнения хранимых процедур, результат которых помещается в OUT параметры команды:
    public void ExecuteNonQueryTest() { 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(); }

    ExecuteReader

    Данный метод возвращает объект OleDbDataReader, который по своему назначению очень близок объекту Recordset из классического ADO. Он использует однонаправленное ForwardOnly чтение данных, реализуя подсоединенную модель доступа. Таким образом, при его использовании необходимо наличие открытого подключения к базе.
    Навигация по строкам результирующего множества осуществляется при помощи метода Read(), который возвращает true в случае, если ещё остались строки и false в противном случае. При вызове метода команды ExecuteReader(), созданный им объект OleDbDataReader не спозиционирован на первой строке результирующего множества и для её прочтения необходимо сначала вызвать метод Read(). Наиболее удобным способом чтения данных из результирующего множества является использование метода Read() совместно с конструкцией while:
    public void ExecuteReaderTest() { 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 Console.WriteLine(tmp); }
    rdr.Close(); //после вызова OleDbDataReader.Close() подключение к БД будет закрыто Assert.AreEqual(ConnectionState.Closed,con.State); }

    ПРИМЕЧАНИЕ.Обратите внимание, что после вызова метода OleDbDataReader.Close() подключение к базе данных будет закрыто. Это произошло потому, что я применил перегруженный метод ExecuteReader() с заданным параметром CommandBehavior.CloseConnection. По умолчанию после выполнения метода OleDbDataReader.Close() подключение к базе данных остается открытым


    ExecuteScalar

    Возвращает единственное значение первой колонки первой строки. Остальные результаты игнорируются. Этот метод полезен для запросов, которые, к примеру, считают количество записей в таблице – соответственно возвращают только одно значение:
    public void ExecuteScalarTest() { 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(); }

    Команды

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

  • Создать активную транзакцию из текущего подключения – метод OleDbConnection.BeginTransaction()

  • Создать объект OleDbCommand, либо используя один из вариантов перегруженного конструктора, либо метод OleDbConnection.CreateCommand()

  • Установить свойство команды Transaction, если оно не было задано в конструкторе

  • Задать текст команды CommandText

  • Для обращения к базе данных у команды есть три метода ExecuteScalar, ExecuteReader и ExecuteNonQuery.

  • Завершить транзакцию OleDbTransaction.Commit() или откатить OleDbTransaction.Rollback() и закрыть подключение.


  • 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, как пишут во многих источниках информации, но и для других баз данных.



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

    В большинстве случаев при выполнении команды требуется задать её параметры. Параметры добавляются в коллекцию 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";

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

    Для использования Ole Db провайдера необходимо подключить соответствующее пространство имен к нашему проекту:
    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


    Работа с BLOB полями

    IBProvider поддерживает работу с двумя типами BLOB полей: содержащих текст и бинарные данные. Не могу не заметить, что при использовании этого провайдера работа с BLOB полями происходит так же, как и с обычными типами данных:
    public void BLOBReadWriteTest() { 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, то так же будет закрыто подключение к базе данных.


    Работа с массивами

    ADO .Net может работать с любыми типами данных. Для тех типов Ole Db, у которых нет прямого отображения на типы данных .Net, используется тип данных DBTYPE_VARIANT. Массивы относятся как раз к таким типам.
    Следующий пример демонстрирует чтение и запись массива из 5 элементов:
    public void ArrayReadWriteTest() { 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[] и т.д.

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

    Один из вариантов защитить строку подключения в своем конфигурационном файле - это воспользоваться Data Protection API (DAPI). Начиная с Windows 2000, DAPI является частью операционной системы.
    Допустим, нам необходимо зашифровать данные, хранящиеся в секции connectionStrings. Для этого мы воспользуемся классом DataProtectionConfigurationProvider:
    Подключим к нашему проекту сборку System.Configuration.dll и используем следующий код:
    public void DataProtectionAPITest() { 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 файлов, поэтому стоит воспользоваться способом хранения защищенных строк подключения в файле конфигурации.

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

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

  • Откройте файл (Enter), появится связанный с данным расширением диалог для настройки подключения:

  • В списке OleDb провайдеров выберете IBProvider v3:

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

  • Задайте параметры подключения по аналогии с рисунком и нажмите кнопку «Проверить подключение»:

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

  • На вкладке дополнительно вы можете задать расширенные свойства подключения:

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

  • Нажмите «Ок» для записи информации о подключении в файл

  • Теперь для того, чтобы использовать подключение, описанное 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):
    Способы хранения строк подключения

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



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

    Для того, чтобы прочитать строку подключения из файла конфигурации, необходимо создать экземпляр класса настроек вашего приложения:
    Properties.Settings s = new Properties.Settings(); //чтение свойства с именем ConnectionString
    Console.WriteLine(s.ConnectionString);
    Для облегчения написания примеров был создан класс ConnectionProvider, который инкапсулирует в себе все, описанные методы подключения.

    Вызов хранимых процедур

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

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

  • Для первого способа используется обычная SQL-инструкция:
    select * from stored_procedure_name(…)
    Результат её выполнения обрабатывается при помощи объекта OleDbDataReader:
    public void StoredProcedureResultSetTest() { 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 stored_procedure_name
    Результат выполнения помещается в OUT параметры команды, которые предварительно необходимо создать:
    public void StoredProcedureOUTParamsTest() { 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(); }

    

        Программирование: Языки - Технологии - Разработка