Манипулирование транзакциями между .NET компонентамиИсточник: progman
Нами будет рассмотрен вопрос о транзакциях, применимых как к Web-приложениям, так и к приложениям на базе Windows. Могли бы вы предоставить больше информации о том, как можно регулировать транзакции между .NET компонентами даже в том случае, если я имею дело с одной базой данных? Если я передаю connection string и сохраняю соединение (connection) открытым, не вызовет ли это издержек? Ответ. Для начала рассмотрим создание транзакций с помощью ADO.NET. Я создал пример приложения, которое вставляет от 1 до 4 order строк в таблицу Order Details в эталонной базе данных Northwind. Приложение использует SQL сервер и SQLClient провайдер данных. Практически то же самое можно сделать с помощью OLE DB провайдера в том случае, если ваша база данных поддерживает транзакции. Вместо того, чтобы просто сослаться на эталонный код транзакции в документации, поясняющей, как работают транзакции ADO.NET, мое эталонное приложение использует простой многоуровневый (multiplayer) подход. Весь код базы данных содержится в отдельном классе компонент (separate components class) в то время, как front end находится в Windows form в отдельном проекте. Компонент базы данных содержит один класс, которые именуется DBStuffDONET. Обратите внимание, что приватная переменная (private variable) содержит connection string. Это хорошо в том случае, когда компонент не требуется использовать с различными базами данных. Тем не менее, компонент имеет совмещенный конструктор (overloaded constructor), который принимает новую connection string, которая может специфицироваться, когда вы инстантиируете (instantiate) класс. После создания connection string variable я также создаю приватную переменную (private variable) для инстансов (Instances) SqlConnection и SqlTransaction. Детали будут рассмотрены позднее. Класс использует функцию RunSQLWithDataSet. Эта функция не участвует в транзакции, она возвращает транзакцию DataSet. В данном примере для простоты я использую статический SQL вместо хранимых процедур (stored procedures). Рассмотрим те функции, которые участвуют в транзакциях, а также некоторые из тех, которые не участвуют. Подпрограмма OpenConnectionTrans создает новый SqlConnection и затем открывает его. После этого он создает транзакцию базы данных, BeginTransaction и устанавливает переменную TransactionCurrent для ссылки (to reference) на транзакцию, как видно их данного примера: Рассмотрим теперь, что произойдет, если вам понадобится сделать вставку (insert) или обновление (update), которые являются частью транзакции. Именно для этой цели предназначена функция RunSQLNonQuery. Когда вы вызываете эту функцию, она создает новый инстанс класса SqlCommand, а затем устанавливает свойства соединения (Connection property) подобную текущему соединению (ConnectionCurrent), а свойства транзакции (Transaction property) такие как у текущей транзакции (TransactionCurrent). После этого выполняется инструкция SQL с помощью метода ExecuteNonQuery, который вызывает малые издержки, поскольку не возвращает никаких данных (см. Рисунок 2). Последняя функция в классе - это RunSQLScalar, которая выполняет инструкцию SQL и возвращает отдельный элемент информации (single piece of information). Эта функция полезна, если вам требуется взять один элемент данных, такой, например, как цена единицы товара (Unit Price). Если вы посмотрите на этот класс внимательнее, то вы увидите, что он stateful. Вы должны инстантиировать класс, открыть соединение (connection) с транзакцией, а затем выполнить код, который вы желаете сделать частью транзакции. После этого вы должны выполнить (commit) или откатить назад (roll back) эту транзакцию и, наконец, закрыть соединение. Все это не так сложно, поскольку вы инстантииируете класс, совершаете работу и закрываете соединение с помощью Windows-based или Web приложения. В моем примере приложение использует клиентский интерфейс на базе Windows и просто инстантиирует класс на время выполнения приложения. На Рисунке №3 показан простой интерфейс построенный на основе Windows forms. Вы выбираете Order из списка вверху, затем кликаете кнопки со стрелками и добавляете нужные строки с подробными данными (detail rows). На Рисунке 3 показана форма с двумя строками подробных данных. В данном примере имеются поля количество и дисконт (discount), которым для целей тестирования присвоены значения 1 и .15 соответственно. После того, как вы ввели line items, кликните кнопку Insert - ADO.NET Trx для того, чтобы добавить новые элементы в таблицу Order Details (Подробные данные о заказе) вашей базы данных. Рассмотрим конструкцию формы. Orders list (cboOrders) и Products list (cboProducts) - это comboboxes, которые предоставляют список заказов и товаров. Вот мелочи, которые помогут вам сэкономить немного времени. Когда я строил форму, я спрятал все директивы ввода данных (data entry controls) и запустил каждую строку, как это требуется. Я присвоил свойству cboProducts.Visible значение False и попытался запустить его, когда запускал строку директив (row of controls). По какой-то причине каждый раз это порождает ошибку времени выполнения. Поэтому мне было интересно узнать, что произойдет, если поместить cboProducts в директиву Panel (Panel control). Я поместил Panel в форму (PnlProduct), поместил туда cboProducts, установил размер панели таким образом,чтобы он соответствовал cboProducts и задаю адрес (location) и видимость (visibility) для этой панели вместо того, чтобы задавать эти параметры для cboProducts. Это сработало хорошо. Combobox контрол находится непосредственно под combobox контролом Orders- это cboProducts. CboProducts динамически помещается наверху первого textbox в строке (как, например, txtProductName_1), когда пользователь вводит данные в эту одну строку. Вы можете исследовать остальную часть интерфейса, выгрузив (downloading) эталонный код, находящийся по ссылке, приведенной в начале статьи. Рассмотрим код, который работает с транзакциями в клиенте (in the client). Когда пользователь кликает кнопку Injsert-ADO.NET Trx для того, чтобы вставить детали заказа (order details), выполняется событие cmdInsertADONet_Click. В первых трех строках события объявляются переменные: После последнего обращения к InsertOrderDetail, вы можете очистить (clean up) транзакцию. Если sStatus ничего не содержит, возникает ошибка и транзакция откатывается назад (rolled back) путем вызова метода RollBackTransaction. В контроле lblMessage будут выданы сообщения об ошибке, как это можно видеть ниже: Вторая часть вопроса месяца связана с производительностью (performance). Безусловно, поддержание соединения в открытом состоянии затрагивает с производительность. Как показано в данном примере, вы можете использовать ADO.NET для обработки транзакций и вам решать, сколько времени вы будете держать соединение открытым. ADO.NET также поддерживает пулинг соединений (connection pooling), поэтому открытие и закрытие соединений вызовет лишь незначительные издержки, если вы будете использовать одну и ту же строку соединения (connection string). |