Столь короткая
глава была написана с единственной целью — ориентировать читателя в нужном направлении.
Ее ограниченный объем не позволит нам даже в общих чертах представить все
средства для работы с базами данных в VB .NET (не говоря уже об их содержательном
обсуждении).
Чтобы
увидеть, как работают элементы, связанные с данными (data-bound controls), воспользуйтесь
мастером Data Form Wizard. Просмотр кода, сгенерированного этой программой,
поможет вам больше узнать о возможностях VB. NET в области работы с базами данных.
В каждой
из предыдущих версий VB появлялась новая модель поддержки баз данных. VB .NET
следует этой давней традиции и представляет новый способ работы с данными —
ADO .NET. При ближайшем рассмотрении выясняется, что название выбрано крайне
неудачно. Почему? Потому что ADO .NET просто не является следующим поколением
ADO! Это совершенно новая модель, не имеющая ничего общего с классическим вариантом
ADO. В частности, для работы с результатами вам придется освоить новую объектную
модель, основанную на объекте DataSet (объект ADO .NET DataSet не привязан к
одной таблице и поэтому обладает значительно большими возможностями, чем, например,
объект ADO RecordSet). Кроме того, модель ADO .NET:
Еще одна
интересная особенность ADO .NET заключается в том, что для таких важных средств,
как двухфазная актуализация данных (commit), потребуется использовать Enterprise
Services (то есть фактически COM+/MTS с .NET-оболочкой).
Автономные
наборы данных: новый подход к работе с базами данных
В VB6 типичное
приложение, использовавшее базы данных, открывало соединение с базой и использовало
его для всех запросов на протяжении жизненного цикла программы. В VB .NET доступ
к базам данных средствами ADO .NET обычно основан на автономных (отсоединенных)
операциях. За этим высокопарным выражением кроется простой смысл: в большинстве
случаев после выборки данных из базы соединение разрывается. В ADO .NET постоянная
связь с источником данных встречается очень редко (при желании вы можете использовать
постоянные соединения классической модели ADO, прибегнув к услугам .NET COM
Interop, однако при этом неизбежно возникают проблемы масштабируемости, издавна
присущие ADO).
Поскольку
программа обычно работает с автономными данными, типичному приложению .NET для
обработки каждого запроса приходится заново подключаться к базе данных. На первый
взгляд это кажется большим шагом назад, но такое впечатление обманчиво. Старый
способ поддержания соединений плохо подходит для мира распределенных систем:
если ваше приложение открывает соединение с базой данных и оставляет его
открытым, серверу приходится поддерживать это соединение до тех пор, пока
клиент его не закроет. Учитывая интенсивную загрузку современных серверов и
пересылку огромных объемов данных, поддержание всех клиентских соединений отрицательно
сказывается на пропускной способности сервера.
Кроме того,
в web-комплексах [ Web-комплексом называется группа компьютеров, обрабатывающих
трафик одного URL. Большинство крупных сайтов обслуживается web-комплексами,
обеспечивающими более эффективное распределение нагрузки.](Web farm) запросы
могут обрабатываться разными компьютерами. Постоянные соединения с web-комплексами
бесполезны, поскольку вы не знаете, какой сервер будет обрабатывать последующие
запросы.
Сборка 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 и выше.
Пространство
имен System.Data.OleDb
Пространство
имен System.Data.OleDb содержит классы, используемые при взаимодействии с OLE
DB-совместимыми базами данных (такими, как Microsoft Access или Microsoft Fox
Pro). Обычно в программах используются классы OleDbConnectl on, OleDbCommand
и OleDbDataReader этого пространства имен. Ниже приведены краткие описания этих
важных классов.
Ниже приведен
пример использования этих трех классов. Наше приложение подключается к базе
данных 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.
Рис.
11.1. Результаты выполнения простого запроса 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, поэтому преобразования данных с потерей точности
допускаются лишь с явным приведением к заданному типу.
Чтение данных
из базы 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.
Нетривиальный пример работы с базами данных в VB .NET
В этом разделе представлено графическое приложение, при помощи которого пользователь
может подключиться к выбранной базе данных SQL, выполнить запрос и получить
его результаты в виде списка. Простоты ради мы отказались от проверки пользовательского
ввода. Программа состоит из трех файлов: двух форм (frmMain и frmResults, см.
рис. 11.2 и 11.3 соответственно) и стандартного модуля Modulel.
Несмотря на свою длину, программа не содержит ничего принципиально нового. На
главной форме размещены четыре текстовых гюля для ввода имени сервера, имени
базы данных, идентификатора пользователя и пароля. При нажатии кнопки Connect
программа динамически выполняет введенную команду во фрагменте, выделенном жирным
шрифтом.
Рис.
11.2. Главная форма приложения
Рис.
11.3. Форма результатов приложения
'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, которые
должны быть доступны для обеих форм. Хотя обычно подобное использование глобальных
данных в окончательных версиях программ не рекомендуется, в данном случае это
позволяет сосредоточить основное внимание на выполнении операций с базой данных.
Вероятно,
наибольший интерес представляет форма 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.
Рис.
11.4. Результат обработки запроса к базе данных Northwind
В этой главе мы постарались дать представление о работе с ADO .NET, однако читатель должен помнить, что перед ним лишь предельно краткий обзор. В частности, мы совершенно не коснулись таких тем, как обновление данных в хранимых процедурах, элементы, связанные с данными, или объекты DataAdapter/DataSet. За подробностями обращайтесь к специализированной литературе.
Когда тот или иной физик использует понятие "физический вакуум", он либо не понимает абсурдности этого термина, либо лукавит, являясь скрытым или явным приверженцем релятивистской идеологии.
Понять абсурдность этого понятия легче всего обратившись к истокам его возникновения. Рождено оно было Полем Дираком в 1930-х, когда стало ясно, что отрицание эфира в чистом виде, как это делал великий математик, но посредственный физик Анри Пуанкаре, уже нельзя. Слишком много фактов противоречит этому.
Для защиты релятивизма Поль Дирак ввел афизическое и алогичное понятие отрицательной энергии, а затем и существование "моря" двух компенсирующих друг друга энергий в вакууме - положительной и отрицательной, а также "моря" компенсирующих друг друга частиц - виртуальных (то есть кажущихся) электронов и позитронов в вакууме.
Однако такая постановка является внутренне противоречивой (виртуальные частицы ненаблюдаемы и их по произволу можно считать в одном случае отсутствующими, а в другом - присутствующими) и противоречащей релятивизму (то есть отрицанию эфира, так как при наличии таких частиц в вакууме релятивизм уже просто невозможен). Подробнее читайте в FAQ по эфирной физике.