Маніпулювання транзакціями між. NET компонентами

Нами буде розглянуто питання про транзакції, застосовних як до Web-додатків, так і до додатків на базі Windows.
Питання Я прочитав вашу колонку у випуску (http://msdn.microsoft.com/msdnmag/issues/02/02/basics/default.aspx), присвяченому COM +, DCOM та MSMQ серіалізациі ст. NET. Ви пишете, що якщо компонент виконує транзакції над окремою базою даних і ви припускаєте, що будете going against тільки однієї бази даних, вам не обов'язково потрібна COM +, щоб реалізувати ці транзакції. Ви можете реалізувати їх за допомогою ADO.NET. Це представляється серйозною зміною ідеології.

Могли б ви надати більше інформації про те, як можна регулювати транзакції між. 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) на транзакцію, як видно їх даного прикладу:

 
Public Sub OpenConnectionTrans()
  ConnectionCurrent = New SqlConnection(sConnectionString)
  ConnectionCurrent.Open()
  TransactionCurrent = ConnectionCurrent.BeginTransaction()
End Sub
Якщо вам потрібна зв'язок (connection) і не потрібна підтримка транзакцій, ви можете викликати OpenConnection:
Public Sub OpenConnection()
  ConnectionCurrent = New SqlConnection(sConnectionString)
  ConnectionCurrent.Open()
End Sub
Якщо прийшов час виконати транзакцію, викликається наступна функція:
Public Sub CommitTransaction()
  TransactionCurrent.Commit()
End Sub
Якщо вам потрібно відкотити назад (roll back) транзакцію, визиввется функція:
Public Sub RollbackTransaction()
  TransactionCurrent.Rollback()
End Sub
Коли ви закінчили працювати з з'єднанням (connection), ви повинні закрити його. Нижченаведена функція закриває з'єднання і видаляє посилання на об'єкт з'єднання (connection object):
Public Sub CloseConnection()
  If ConnectionCurrent Is Nothing Then
  Exit Sub
  End If
  If ConnectionCurrent.State = ConnectionState.Open Then
  ConnectionCurrent.Close()
  ConnectionCurrent = Nothing
  End If
End Sub

Розглянемо тепер, що станеться, якщо вам знадобиться зробити вставку (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. 

У перших трьох рядках події оголошуються змінні:
Dim sSQL As String
Dim sStatus As String = “”
Dim sOrderID As String
У цій точці викликається метод CloseConnection (Закрити з'єднання). Якщо з'єднання немає, помилка не виникає, але якщо з'єднання є, то воно буде закрито:
oDB.CloseConnection()
Потім відкривається з'єднання, і транзакція стартує:
oDB.OpenConnectionTrans()
Наступні кілька рядків коду досить прості, вони задають контроли, перевіряють, чи було введено ім'я товару (product name), і отримують поточний замовлення:
lblMessage.Text = “”
If txtProductName_1.Text <> “” Then
sOrderID = cboOrders.SelectedValue
If sOrderID = “” Then
lblMessage.Text = "You must select an order to add line items to"
Exit Sub
End If
Наступні кілька рядків вставляють деталі замовлення в базу даних шляхом виклику функції InsertOrderDetail. Кожна з цих рядків викликається в наступному форматі, де значення з форми передаються у функцію:
“Insert 1st order
sStatus = InsertOrderDetail(sOrderID, _
  txtProductID_1.Text, _
  txtUnitPrice_1.Text, _
  txtQuantity_1.Text, _
  txtDiscount_1.Text)
Єдині речі, які змінюються для подальших викликів, – це перевірка sStatus з тим, щоб упевнитися, що sStatus не містить повідомлень про помилки, а директива (control) txtProductID не є порожній. Якщо ці критерії задоволені, здійснюється звернення до InsertOrderDetail:
“Insert 2nd order
If sStatus = “” And txtProductID_2.Text <> “” Then
sStatus = InsertOrderDetail(sOrderID, _
  txtProductID_2.Text, _
  txtUnitPrice_2.Text, _
  txtQuantity_2.Text, _
  txtDiscount_2.Text)
End If
Я опустив виклики для третьої і четвертої рядків, тому що вони ідентичні останнього рядка, за винятком того, що вони посилаються на різні директиви.

Після останнього звернення до InsertOrderDetail, ви можете очистити (clean up) транзакцію. Якщо sStatus нічого не містить, виникає помилка і транзакція відкочується назад (rolled back) шляхом виклику методу RollBackTransaction. У контролі lblMessage будуть видані повідомлення про помилку, як це можна бачити нижче:
“Cleanup and rollback / commit
If sStatus <> “” Then
lblMessage.Text = _
  “Rolled back transaction due to error ” _
  on row ” _
  & ” – ” & sStatus
oDB.RollbackTransaction()
Exit Sub
End If
І, нарешті, якщо помилок не виникає, то для завершення транзакції викликається метод CommitTransaction. LblMessage змінюється таким чином, щоб відобразити успішну запис в базу даних, і з'єднання закривається:
oDB.CommitTransaction()
lblMessage.Text = “Line items inserted ok”
oDB.CloseConnection()
End If
Функція InsertOrderDetail дуже проста. Заголовок функції виглядає наступним чином:
Function InsertOrderDetail(ByVal sOrderID As String, _
  ByVal sProductID As String, ByVal sUnitPrice As String, _
  ByVal sQuantity As String, ByVal sDiscount As String) _
  As String
Потім створюються дві змінні
Dim sSQL As String
Dim sInsertStatus As String
Після цього створюється інструкція SQL з використанням переданих параметрів:
sSQL = “INSERT INTO [Order Details] ” _
  “(OrderID, ProductID, UnitPrice, Quantity,” _ 
  “Discount) ” 
sSQL &= “VALUES(” & sOrderID & “,” & sProductID & “,” _
  & sUnitPrice & “,”
sSQL &= sQuantity & “,” & sDiscount & “)”
Блок Try / Catch містить виклик RunSQLNonQuery, яка дійсно виконує SQL. Зверніть увагу, що генерується виключення, якщо sInsertStatus встановлюється рівним будь-якому значенню, відмінному від пробіл. Це дає можливість кодом перехоплювати в блоці Catch (Catch Block) всі умови помилки (error conditions).
  Try
  sInsertStatus = oDB.RunSQLNonQuery(sSQL)
  If sInsertStatus <> “” Then
  Throw New System.Exception(sInsertStatus)
  End If
  Catch exc As Exception
  Return exc.Message
  End Try
End Function
Все дуже просто. Ви можете бачити, що ADO.NET дозволяє досить просто кодувати транзакції, якщо ваша база даних підтримує їх.

Друга частина питання місяці пов'язана з продуктивністю (performance). Безумовно, підтримка з'єднання у відкритому стані зачіпає з продуктивність. Як показано в даному прикладі, ви можете використовувати ADO.NET для обробки транзакцій і вам вирішувати, скільки часу ви будете тримати з'єднання відкритим. ADO.NET також підтримує пулинг сполук (connection pooling), тому відкриття і закриття сполук викличе лише незначні витрати, якщо ви будете використовувати одну й ту ж рядок з'єднання (connection string).

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*