Автономные наборы данных: новый подход к работе с базами данных
В VB6 типичное приложение, использовавшее базы данных, открывало соединение с базой и использовало его для всех запросов на протяжении жизненного цикла программы. В VB .NET доступ к базам данных средствами ADO .NET обычно основан на автономных (отсоединенных) операциях. За этим высокопарным выражением кроется простой смысл: в большинстве случаев после выборки данных из базы соединение разрывается. В ADO .NET постоянная связь с источником данных встречается очень редко (при желании вы можете использовать постоянные соединения классической модели ADO, прибегнув к услугам .NET COM Interop, однако при этом неизбежно возникают проблемы масштабируемости, издавна присущие ADO).
Поскольку программа обычно работает с автономными данными, типичному приложению .NET для обработки каждого запроса приходится заново подключаться к базе данных. На первый взгляд это кажется большим шагом назад, но такое впечатление обманчиво. Старый способ поддержания соединений плохо подходит для мира распределенных систем: если ваше приложение открывает соединение с базой данных и оставляет его открытым, серверу приходится поддерживать это соединение до тех пор, пока клиент его не закроет. Учитывая интенсивную загрузку современных серверов и пересылку огромных объемов данных, поддержание всех клиентских соединений отрицательно сказывается на пропускной способности сервера.
Кроме того, в web-комплексах [ Web-комплексом называется группа компьютеров, обрабатывающих трафик одного URL. Большинство крупных сайтов обслуживается web-комплексами, обеспечивающими более эффективное распределение нагрузки. ](Web farm) запросы могут обрабатываться разными компьютерами. Постоянные соединения с web-комплексами бесполезны, поскольку вы не знаете, какой сервер будет обрабатывать последующие запросы.
Форма результатов приложения
'frmMain.vb
Imports System.Data.SqlClient
Public Class frmMain
Inherits System.Windows.Forms.Form #Region "Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'Вызов необходим для работы дизайнера форм Windows
InitializeComponent()
' Дальнейшая инициализация выполняется
' после вызова InitializeComponent()
End Sub
' Форма переопределяет Dispose для очистки списка компонентов.
Protected Overloads Overrides
Sub Dispose(ByVal disposing As Boolean)
If Disposing Then
If Not (components Is Nothing) Then
components. Dispose()
End If
End If
MyBase.Dispose(Disposing) End Sub
Private WithEvents Label1 As System.Windows.Forms.Label
Private WithEvents Label2 As System.Windows.Forms.Label
Private WithEvents Label3 As System.Windows.Forms.Label
Private WithEvents Label4 As System.Windows.Forms.Label
Private WithEvents btnConnect As System.Windows.Forms.Button
Private WithEvents txtUID As System.Windows.Forms.TextBox
Private WithEvents txtPassword As System.Windows.Forms.TextBox
Private WithEvents txtDatabase As System.Windows.Forms.TextBox
Private WithEvents txtServer As System.Windows.Forms.TextBox
' Необходимо для работы дизайнера форм Windows
Private components As System.ComponentModel.Container
' ВНИМАНИЕ: следующий фрагмент необходим для дизайнера форм Windows
' Для его модификации следует использовать дизайнер форм.
' Не изменяйте его в редакторе!
<System.Diagnostics.DebuggerStepThrough()>
Private Sub _ Initial izeComponent()
Me.Label4 = New System.Windows.Forms.Label ()
Me.txtPassword = New System.Windows.Forms.TextBox()
Me.Label 1 = New System.Windows.Forms.Label ()
Me.txtServer = New System.Windows.Forms.TextBox()
Me.Label2 = New System.Windows.Forms.Label ()
Me.Labels = New System.Windows.Forms.Label ()
Me.txtUID - New System.Windows.Forms.TextBox()
Me.txtDatabase = New System.Windows.Forms.TextBox()
Me.btnConnect = New System.Windows.forms.Button()
Me.SuspendLayout()
'Label4
Me.Label4.Location = New System.Drawing.Point(24.176)
Me.Label 4.Name = "Label4"
Me.Label4.Size = New System.Drawing.Size(82.19)
Me.Label4.TabIndex = 0
Me.Label4.Text = "Password:"
Me.Label4.TextAlign = System.Drawi ng.ContentAlignment.MiddleRight
'txtPassword
Me.txtPassword.Location = New System.Drawing.Point(168.168)
Me ..txtPassword. Name = "txtPassword"
Me.txtPassword.PasswordChar = ChrW(42)
Me.txtPassword.Size = New System.Drawing.Size(205.22)
Me.txtPassword.Tablndex = 3
Me.txtPassword.Text = ""
'Label 1
Me.Label 1.Location = New System.Drawing.Point(24. 32)
Me.Label 1.Name = "Label1"
Me.Label 1.Size = New System.Drawing.SizeC82. 20)
Me.Label 1.Tablndex =0
Me.Label 1.Text = "Server:"
Me.Label 1.TextAli gn = System.Drawi ng.ContentAlignment.Mi ddleRight
'txtServer
Me.txtServer.Location - New System.Drawing.Point(168, 24}
Me.txtServer.Name = "txtServer"
Me.txtServer.Size = New System.Drawing.Size(205. 22)
Me.txtServer.Tablndex = 0
Me.txtServer.Text = ""
'Label 2
Me.Label2.Location = New System.Drawing.Point(24. 80)
Me.Label 2.Name = "Label 2"
Me.Label2.Size = New System.Drawing.Size(82, 20)
Me.Label2.Tablndex = 0
Me.Label 2.Text = "Database:"
Me.Label 2.TextAlign = System.Drawi ng.ContentAlignment.Mi ddleRight
'Label3
Me. Labels.Anchor = System.Windows.Forms.AnchorStyles.None
Me.Label3.Location = New System.Drawing.Point(24. 128)
Me.Labels.Name = "Label 3"
Me.Labels.Size = New System.Drawing.Size(82. 20)
Me.Labels.Tablndex = 0
Me.Labels.Text = "User ID:"
Me.Label 3.TextAli gn = System.Drawi ng.ContentAlignment.Mi ddleRi ght
'txtUID
Me.txtUID.Location = New System.Drawing.Point(168, 120)
Me.txtUID.Name = "txtUID"
Me.txtUID.Size - New System.Drawing.Size(205, 22)
Me.txtUID.Tablndex = 2
Me.txtUID.Text = ""
'txtDatabase
Me.txtDatabase.Location = New System.Drawing.Point(168. 72)
Me.txtDatabase.Name = "txtDatabase"
Me.txtDatabase.Size = New System.Drawing.Size(205. 22)
Me.txtDatabase.Tablndex = 1
Me.txtDatabase.Text = ""
'btnConnect
Me.btnConnect.Location = New System.Drawing.Point(160. 232)
Me.btnConnect.Name = "btnConnect"
Me.btnConnect.Size = New System.Drawing.Size(92, 30)
Me.btnConnect.Tablndex = 4
Me. btnConnect.Text = "SConnect"
'frmMain
Me.AutoScaleBaseSize = New System.Drawing.Size(6. 15)
Me.ClientSize - New System.Drawing.Size(408, 280)
Me.Controls.AddRange(New _
System.Wi ndows.Forms.Control(){Me.btnConnect,_
Me.txtPassword. Me.txtUID. Me.txtDatabase.
Me.txtServer.Me
.Label 4.
Me.Label3.Me
.Label 2.
Me.Label 1})
Me.Name - "frmMain" Me.Text = "DB Connector"
Me.ResumeLayout(False) End Sub
#End Region
Private Sub btnConnect_C1ick(ByVal sender As System.Object,_
ByVal e As System.EventArgs) Handles btnConnect.Click
Try
mySQLConn = New SqlConnectionC'user id=" & txtUID.Text &
";password="&txtPassword.Text & _ ";database="&txtDatabase.Text & _
";server="&txtServer.Text)
mySQLConn.Open() dbCmd.Connect!on = mySQLConn
Dim frmChild As New frmResults() frmChild.Show()
Catch except As Exception MsgBox(_
"Failed to connect for the following reason:<" & _ except.Message & ">")
End Try
End Sub
End Class
Модуль содержит следующий код:
Imports System.Data.SqlClient Module main
' Глобальные определения
Public mySQLConn As SqlConnection
Public dbReader As SqlDataReader
Public dbCmd As SqlCommand = New SqlCommand()
End Module
Модуль Modulel содержит только глобальные определения различных объектов SQL, которые должны быть доступны для обеих форм. Хотя обычно подобное использование глобальных данных в окончательных версиях программ не рекомендуется, в данном случае это позволяет сосредоточить основное внимание на выполнении операций с базой данных.
Классы сборки System.Data.DLL
Сборка System.Data.DLL содержит большое количество классов, разделенных на пять пространств имен работы с данными с дополнительным пространством
System.Xml. Вспомогательное пространство System.Data.SqlTypes содержит структурные типы, соответствующие типам данных SQL Server (например, Sql Money и SqlDateTime).
Поскольку типы данных SQL реализованы в виде структурных типов, их преобразования отличаются большей эффективностью по сравнению с другими языками — например, по сравнению с Java, где типы SQL реализованы в виде ссылочных типов.
Другое вспомогательное пространство имен, System.Data .Common, содержит классы, часто используемые при обращениях к источнику данных. В этой главе основное внимание уделяется пространствам имен System. Data.OleDb и System. Data. SqlCLient, выполняющим непосредственную работу. Классы этих пространств имен используют средства System. Data. Common, включая класс DataAdapter. Класс DataAdapter представляет соединение с базой данных, используемое при заполнении набора данных или обновлении источника, а также некоторые стандартные команды при операциях с базами данных.
Пространства имен System.Data.OleDb и System.Data.SqtCLient обладают сходной функциональностью, с одним исключением — классы System.Data.OleOb предназначены для подключения к источникам данных OLE DB, а классы System.Data.SqlCHent ориентированы на Microsoft SQL Server версии 7 и выше.
Содержание
|
Вперед
|
Нетривиальный пример работы с базами данных в VB .NET (часть 1)
В этом разделе представлено графическое приложение, при помощи которого пользователь может подключиться к выбранной базе данных SQL, выполнить запрос и получить его результаты в виде списка. Простоты ради мы отказались от проверки пользовательского ввода. Программа состоит из трех файлов: двух форм (frmMain и frmResults, см. Рисунок 11.2 и 11.3 соответственно) и стандартного модуля Modulel.
Несмотря на свою длину, программа не содержит ничего принципиально нового. На главной форме размещены четыре текстовых гюля для ввода имени сервера, имени базы данных, идентификатора пользователя и пароля. При нажатии кнопки Connect программа динамически выполняет введенную команду во фрагменте, выделенном жирным шрифтом.
Вероятно, наибольший интерес представляет форма frmResults (комментарии следуют после листинга). Ключевое место в этой форме занимает метод btnQuery_Click, выделенный жирным шрифтом:
' frmResults.vb
Imports System.Data.SqlClient
Public Class frmResults
Inherits System.Windows.Forms.Form fRegion "Windows Form Designer generated code "
Public Sub New() MyBase.New()
'Вызов необходим для работы дизайнера форм Windows
InitializeComponent()
' Дальнейшая инициализация выполняется
' после вызова InitializeComponent()
End Sub
' Форма переопределяет Dispose для очистки списка компонентов.
Public Overrides Sub Dispose()
MyBase.Dispose()
If Not (components Is Nothing) Then components.
Dispose()
End If
End Sub
Private WithEvents txtQuery As System.Windows.Forms.TextBox
Private WithEvents btnQuery As System.Windows.Forms.Button
Private WithEvents IstData As System.Windows.Forms.ListBox
' Необходимо для работы дизайнера форм Windows
Private components As System.ComponentModel.Container
' ВНИМАНИЕ: следующий фрагмент необходим для дизайнера форм Windows
' Для его модификации следует использовать дизайнер форм.
' Не изменяйте его в редакторе!
<System.Diagnostics.DebuggerStepThrough()>
Private Sub _
Initial izeComponent()
Me.btnQuery = New System.Windows.Forms.Button()
Me.txtQuery = New System.Windows.Forms.TextBox()
Me.IstData = New System.Windows.Forms.ListBox()
Me.SuspendLayout()
'btnQuery
Me. btnQuery. Font = NewSystem. Orawing. Font ("Microsoft Sans Serif"._
8.5!.System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point,CType(0. Byte))
Me.btnQuery.Location = New System.Drawing.Point(440. 0)
Me.btnQuery.Name = "btnQuery"
Me.btnQuery.Size = New System.Drawing.Size(56. 24)
Me.btnQuery.Tablndex = 2
Me.btnQuery.Text = "&Execute"
'txtQuery
Me. txtQuery. Font=New System. Drawing. Font ("Microsoft Sans Serif", _
8.5!. System.Drawing.FontStyle.Regular.
System.Drawi ng.Graphi csUnit.Point.CTypet 0. Byte))
Me.txtQuery.Location = New System.Drawing.Point(8. 0)
Me.txtQuery.Name = "txtQuery"
Me.txtQuery.Size = New System.Drawing.Size(432, 20)
Me.txtQuery.Tablndex = 1
Me.txtQuery.Text = "TextBox1"
'IstData
Me.lstData.ColumnWidth = 120
Me.IstData.Location = New System.Drawing.Point(8. 32)
Me.lstData.MultiColumn = True
Me.lstData.Name = "IstData"
Me.lstData.Size = New System.Drawing.Size(488. 355)
Me.lstData.Tablndex = 3
'frmResults
Me.AutoScaleBaseSize = New System.Drawing.Size(5. 13)
Me.ClientSize = New System.Drawing.Size(504. 397)
Me.Controls.AddRange(New System.Windows.Forms.Control()
{Me.lstOata. Me.btnQuery, Me.txtQuery})
Me.Name = "frmResults"
Me.Text = "Query Window"
Me.ResumeLayout(False)
End Sub
#End Region
Private Sub btnQuery_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnQuery.Click
Try
dbCmd.CommandText = txtQuery.Text
dbReader=dbCmd. ExecuteReader (CoimandBehavior. Singl eResult)
' Получить схему таблицы
Dim dtbllnfo As DataTable = dbReader.GetSchemaTable()
' Служебная переменная для перебора записей
Dim rwRow As DataRow
Dim strHeaders As System.Text.StringBuilder - _
New System.Text.StringBuilder()
Dim strData As System.Text.StringBuilder = New _
System.Text.StringBuilder()
Dim typTypesCdtbllnfo.Columns.Count) As Type
Dim intCounter As Integer = 0
' Перебрать все записи метаданных
For Each rwRow In dtblInfo.Rows
' Определить тип
typTypes(intCounter)= rwRow("DataType") intCounter +=1
' Включить в строку имя поля
strHeaders.Append("<" & rwRow(0) & ">" & vbTab) Next
' Занести в список заголовочную строку
1stData.Items.Add(strHeaders.ToString())
' Перебор записей данных
Do While dbReader.Read()
' Перебор полей записи
For intCounter = 0 To (dbReader.FieldCount - 1)
' Включить содержимое поле в выходную строку
strData.Append(GetProperType(dbReader,intCounter,_
typTypes(intCounter)) & vbTab) Next
' Включить строку в список
1stData.Items.Add(strData.ToString())
' Очистить объект StringBuilder strData = New System.Text.StringBuilder()
Loop Catch except As Exception
MsgBoxt"Error:" & except.Message)
End Try
End Sub
' Функция получает данные конкретного столбца.
Private Function GetProperType(ByVal dr As SqlDataReader.
ByVal intPos As Integer, ByVal typType As Type) As Object
' Проверить тип поля, затем получить значение Select
Case typType.Name Case "String"
' Преобразовать и вернуть
Return CType(dr.GetString(intPos).String)
Case "Int32"
' Преобразовать и вернуть
Return
CType(dr.Get!nt32(intPos). Int32)
' Здесь следовало бы организовать проверку всех
' остальных типов и возврат соответствующих значений.
' Мы выбрали простой путь и ограничились проверкой
' двух самых распространенных типов
Case Else
Return "<Unsupported Type>"
End Select
End Function
End Class
'При нажатии кнопки в объект команды SQL
'заносится текст, введенный пользователем в текстовом поле:
dbCmd.CommandText = txtQuery.Text
(в настоящем примере пропущена проверка данных, необходимая в любой реальной программе).
Далее объявляются объекты, используемые при чтении и выводе имен полей и их значений:
Dim dtbllnfo As DataTable = dbReader.GetSchemaTable()
Dim rwRow As DataRow
Dim strHeaders As System.Text.StringBuilder = New _
System.Text.StringBuilder()
Dim strData As System.Text.StringBuilder = New _
System.Text.Stri ngBui1der()
Dim typTypes(dtblInfo.Columns.Count) As Type
Поскольку в этом приложении структура базы данных не известна заранее, мы получаем ее описание при помощи метода GetSchemaTable(). Этот метод возвращает объект DataTable с метаданными (описаниями полей записей полученного набора). Метаданные содержат информацию о количестве полей в записи, их именах и типах. На основании этой информации можно запросить и вывести данные из любой доступной базы данных. Помните, что в режиме Option Strict On (который всегда должен быть активным) для вызова правильной функции GetXXX() объекта DataReader необходимо знать тип поля. По соображениям эффективности в приведенном примере использованы две переменные типа Stri ngBui I der (см. ниже). Информация, необходимая для вывода данных в списке, извлекается в цикле:
Dim intCounter As Integer =0 For Each rwRow In dtbllnfo.Rows
typTypes(intCounter) = rwRow("'DataType")
intCounter += 1
strHeaders.Append("<" & rwRow(0) & ">" & vbTab) Next
Записи DataTable перебираются в цикле For Each. Типы полей сохраняются в массиве typTypes и затем присоединяются к объекту StringBuilder для последующего вывода всех имен столбцов за одну операцию (однократное обновление свойства выполняется быстрее многократных). Также обратите внимание на использование имени поля в вызове rwRow( "DataType") — структура таблицы может измениться, что приведет к изменению номера поля DataType. После завершения цикла у нас появится вся необходимая информация об именах и типах всех полей, и мы сможем перейти к ее выводу в конструкции с вложенным циклом:
Do While dbReader.Read()
For intCounter = 0 To (dbReader.FieldCount = 1)
strData.Append(GetProperType(dbReader.intCounter,
typTypes(intCounter)) & vbTab) Next
1stData.Items.Add(strData.ToString()) strData = New
System.Text.StrlngBuilder() Loop
Первая часть цикла напоминает аналогичные конструкции из предыдущих примеров — мы перебираем все поля записи, определяем тип каждого поля и выводим данные в списке перед следующим вызовом Read. Для упрощения этой задачи была написана вспомогательная функция GetProperType().
На Рисунок 11.4 показан результат выборки данных из базы Northwind.
Почему ADO .NET — не ADO++
В каждой из предыдущих версий VB появлялась новая модель поддержки баз данных. VB .NET следует этой давней традиции и представляет новый способ работы с данными — ADO .NET. При ближайшем рассмотрении выясняется, что название выбрано крайне неудачно. Почему? Потому что ADO .NET просто не является следующим поколением ADO! Это совершенно новая модель, не имеющая ничего общего с классическим вариантом ADO. В частности, для работы с результатами вам придется освоить новую объектную модель, основанную на объекте DataSet (объект ADO .NET DataSet не привязан к одной таблице и поэтому обладает значительно большими возможностями, чем, например, объект ADO RecordSet). Кроме того, модель ADO .NET:
не поддерживает курсоры на стороне сервера. Динамические курсоры ADO в ней не поддерживаются;
базируется на языке XML [ Во внутреннем представлении классов ADO .NET используется оптимизированный формат, но весь обмен данными происходит в формате XML. ] (что позволяет работать через Интернет, даже если клиент находится за. брандмауэром (firewall));
входит в сборку .NET System. Data. DLL, а не реализуется на уровне языка; вряд ли будет поддерживать старые клиенты Windows 95.
Еще одна интересная особенность ADO .NET заключается в том, что для таких важных средств, как двухфазная актуализация данных (commit), потребуется использовать Enterprise Services (то есть фактически COM+/MTS с .NET-оболочкой).
Пространство имен System.Data.OleDb
Пространство имен System.Data.OleDb содержит классы, используемые при взаимодействии с OLE DB-совместимыми базами данных (такими, как Microsoft Access или Microsoft Fox Pro). Обычно в программах используются классы OleDbConnectl on, OleDbCommand и OleDbDataReader этого пространства имен. Ниже приведены краткие описания этих важных классов.
Класс OleDbCommand: класс представляет команды SQL, применяемые к базе данных OLE DB. Вместе с командой хранятся все параметры и дополнительная информация, необходимая для обработки запроса.
Класс OleDbDataReader: используется после получения данных от источника при помощи двух классов, описанных выше. Является специализированной формой класса потока ввода (см. главу 9) и умеет только читать данные, возвращаемые объектом OleDbCommand. Аналогом объекта DataReader в ADO является набор записей, хранящийся на сервере, доступный только для чтения и поддерживающий только перебор в прямом направлении.
Ниже приведен пример использования этих трех классов. Наше приложение подключается к базе данных Northwind, входящей в поставку Access и современных версий SQL Server.
1 Imports System.Data.OleDb
2 Module Modulel
3 Sub Maint)
4 Dim myAccessConn As OleDbConnection
5 Dim dbReader As OleDbDataReader
6 Dim dbCmd As OleDbCommand =New OleDbCommand(
7 "SELECT Employees.FirstName.Employees.LastName FROM Employees")
8 Try
9 ' Открыть соединение
10 myAccessConn = New OleDbConnection(
11 "Provider=Microsoft.Jet.OLEDB.4.0;" &_
12 "Data Source=C:\Program Files \Microsoft _
Office\0ffice\SamplesNNorthwind.mdb")
13 myAccessConn.Open()
14 dbCmd.Connection = myAccessConn
15 dbReader = dbCmd.ExecuteReader(CommandBehavior.SingleResult)
16 Do While dbReader.Read()
17 Console.WriteLine(dbReader.GetString(0) & " " & _
dbReader.GetString(1))
18 Loop
19 Console.ReadLine()
20 Catch e As Exception
21 MsgBox(e.Message)
22 End Try
23 End Sub
24 End Module
Результаты, полученные при запуске этого приложения, показаны на Рисунок 11.1.
Результат обработки запроса к базе данных Northwind
В этой главе мы постарались дать представление о работе с ADO .NET, однако читатель должен помнить, что перед ним лишь предельно краткий обзор. В частности, мы совершенно не коснулись таких тем, как обновление данных в хранимых процедурах, элементы, связанные с данными, или объекты DataAdapter/DataSet. За подробностями обращайтесь к специализированной литературе.
Результаты выполнения простого запроса SQL
Хотя наше приложение всего лишь выводит список работников Northwind, его код типичен для подключения к любой базе данных при помощи .NET-провайдера OLE DB, предоставленного VB .NET. В строке 1 для упрощения дальнейших ссылок импортируется пространство имен System. Data. 0leDb. В строках 4 и 5 объявляются две объектные переменные. Объект 0leDbConnecti on инкапсулирует текущее соединение к провайдеру OLE DB и в конечном счете к базе данных (строки 10-12). Объект 0leDbDataReader инкапсулирует рабочие данные. В отличие от объектов RecordSet эти данные не обязаны относиться к одной таблице (хотя в нашем примере это именно так). Строка 6 определяет запрос SQL, хранящийся в объекте OleDbCommand. Использована версия конструктора с параметром типа Stri ng, в котором передается команда SQL, — в нашем случае это простейший из всех возможных запросов. В строке 10 создается соединение с базой данных. При вызове конструктора передается строка с именем провайдера OLE DB. Значение берется из реестра Windows и не является частью .NET (в нашем примере используется стандартный провайдер для Access). Также обратите внимание на жесткую кодировку местонахождения базы данных Northwind; в нашем примере выбран каталог, используемый по умолчанию при установке Office. Если на вашем компьютере база данных Northwind находится в другом каталоге, отредактируйте эту строку.
Затем созданное соединение открывается. Поскольку эта операция по различным причинам может завершиться неудачей, программный код открытия и чтения из базы данных заключается в блок Try-Catch. После успешного вызова Ореn() (строка 13) соединение можно использовать в программе (выполнение этих операций в конструкторе позволило бы сократить программу на несколько строк). Объект OleDbCommand пока не знает, какое соединение он должен использовать, поэтому открытое соединение с базой данных назначается свойству Connecti on объекта OleDbCommand (строка 14). Одно из преимуществ подобного решения заключается в том, что оно позволяет использовать один объект команды с несколькими соединениями.
Команда выполняется методом ExecuteReader() объекта 0leDbCommand (строка 15). Мы используем метод ExecuteReader, поскольку остальные методы Execute возвращают данные в формате XML и традиционные наборы записей, обрабатываемые менее эффективно. В строке 14 значение перечисляемого типа CommandBehavior. SingleResul t передается методу ExecuteReader в качестве параметра. Флаг Si ngl eResult означает, что команда должна выбрать из базы данных все записи результата. Другие флаги позволяют ограничить выборку одной или несколькими записями. Прочитанные записи перебираются в цикле в строках 16-18.
Код перебора записей эквивалентен следующему фрагменту VB6/ADO:
Do While Not rs.EOF
Print rs(0)
rs.MoveNext() Loop
Использование метода Read предотвращает одну распространенную ошибку, часто допускаемую программистами VB6 ADO, которые забывают перейти к следующей записи методом MoveNext. Все операции с одной записью выполняются между вызовами Read, поскольку после вызова Read вернуться к содержимому предыдущей записи уже не удастся.
В цикле вызываются различные методы GetXXX объекта 01 eDbReader, возвращающие значение поля с заданным индексом (нумерация полей записи начинается с 0). Таким образом, вызов
dbReader.GetString()
возвращает значение второго столбца в формате String. Вместо индекса при вызове GetStrlng можно указать имя столбца, но этот вариант менее эффективен.
Перед получением значения поля необходимо указать правильный тип. Мы всегда про-граммируем в режиме Option Strict On, поэтому преобразования данных с потерей точности допускаются лишь с явным приведением к заданному типу.
System. Data.SqlClient
Чтение данных из базы SQL Server происходит аналогичным образом — пространства имен OleDb и SqlClient имеют практически одинаковый синтаксис. Ниже приведена версия предыдущей программы для SQL Server:
Imports System.Data.SqlClient
Module Modulel
Sub Main()
Dim mySQLConnString As String
Dim mySQLConn As SqlConnection
Dim dbReader As SqlDataReader
Dim dbCmd As SqlCommand = New SqlCommand(
"SELECT Employees.FirstName.Employees.LastName FROM
Employees") Try
mySQLConnString = _
"uid-test:password=apress;
database=northwind:server=Apress"
mySQLConn = New SqlConnection(mySQLConnString)
mySQLConn.Open()
dbCmd.Connection = mySQLConn
dbReader = dbCmd.ExecuteReader(CommandBehavior.SingleResult)
Do While dbReader.Read()
' Вывести данные в консольном окне
Console.WriteLinetdbReader.GetString(0) & "." &_
dbReader.GetString(1))
Loop
Catch e As Exception MsgBox(e.Message)
End Try
Console. ReadLine()
End Sub
End Module
Основное различие (помимо имен классов) наблюдается в формате строки соединения. Предполагается, что на сервере Apress имеется учетная запись с паролем apress. При подключении к SQL Server в строке соединения указывается идентификатор пользователя, пароль, сервер и имя базы данных. Передавая эту информацию, мы получаем объект соединения. Конечно, лишь простейшие запросы формулируются в виде простой строки; в любом сколько-нибудь нетривиальном случае строку запроса приходится строить из отдельных фрагментов.
Несмотря на разный формат строк соединения, в приложениях SQL и OLE DВ используется одна и та же программная модель ADO .NET — это весьма существенное преимущество. Наличие общих интерфейсов IDbConnection, IdbCommand и т. д. значительно упрощает написание обобщенного кода в ADO .NET.
Вызов хранимой процедуры
В следующем примере используется хранимая процедура с именем getalbumname. Процедура вызывается с одним параметром и выбирает из базы данных albums запись альбома с заданным именем:
create procedure getalbumbyname
@albumname varchar(255) As
select *from albums where albumname = @albumname
Выборка данных с использованием хранимой процедуры организована аналогично простому запросу к базе данных Northwind:
Dim dbCmd As SqlCommand = New SqlCommand(
"execute getalbumbyname 'Operation Mindcrime'")
Try
mySQLConn =New SqlConnection(
"user id=sa:password=password;" & _
"database=albums;server=i-ri3")
mySQLConn.Open()
dbCmd.Connection = mySQLConn
dbReader = dbCmd.ExecuteReader(CommandBehavior.SingleResult)
' И т.д.
End Try
Как видите, программа почти не изменилась, разве что команда SQL, использовавшаяся для создания объекта Sql Command, превратилась в команду вызова хранимой процедуры getalbumbyname, которой в качестве параметра передается имя интересующего нас альбома. Конечно, после вызова ExecuteReader цикл перебора записей не нужен, поскольку мы точно знаем, что хранимая процедура возвращает всего одну запись.
Вместо того чтобы передавать параметр хранимой процедуры в строке вызова, можно воспользоваться коллекцией Parameters объекта SQLCommand. Мы решили, что вариант с непосредственной передачей параметров в команде SQL проще. Конечно, это возможно лишь в том случае, если значение параметра известно во время написания программы, в противном случае приходится использовать коллекцию Parameters.