Глава 11. Поддержка баз данных в VB .NET

Глава №11.

Поддержка баз данных B VB.NET

Столь короткая глава была написана с единственной целью — ориентировать читателя в нужном направлении. Ее ограниченный объем не позволит нам даже в общих чертах представить все средства для работы с базами данных в VB .NET (не говоря уже об их содержательном обсуждении).

Чтобы увидеть, как работают элементы, связанные с данными (data-bound controls), воспользуйтесь мастером Data Form Wizard. Просмотр кода, сгенерированного этой программой, поможет вам больше узнать о возможностях VB. NET в области работы с базами данных.

Почему ADO .NET — не ADO++

В каждой из предыдущих версий VB появлялась новая модель поддержки баз данных. VB .NET следует этой давней традиции и представляет новый способ работы с данными — ADO .NET. При ближайшем рассмотрении выясняется, что название выбрано крайне неудачно. Почему? Потому что ADO .NET просто не является следующим поколением ADO! Это совершенно новая модель, не имеющая ничего общего с классическим вариантом ADO. В частности, для работы с результатами вам придется освоить новую объектную модель, основанную на объекте DataSet (объект ADO .NET DataSet не привязан к одной таблице и поэтому обладает значительно большими возможностями, чем, например, объект ADO RecordSet). Кроме того, модель ADO .NET:

  • проектировалась как модель с полиостью автономной архитектурой (хотя классы DataAdapter, Connection, Command и DataReader остаются ориентированными на соединение);.
  • не поддерживает курсоры на стороне сервера. Динамические курсоры ADO в ней не поддерживаются;
  • базируется на языке XML [ Во внутреннем представлении классов ADO .NET используется оптимизированный формат, но весь обмен данными происходит в формате XML.] (что позволяет работать через Интернет, даже если клиент находится за. брандмауэром (firewall));
  • входит в сборку .NET System. Data. DLL, а не реализуется на уровне языка;
  • вряд ли будет поддерживать старые клиенты Windows 95.

Еще одна интересная особенность 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.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 этого пространства имен. Ниже приведены краткие описания этих важных классов.

  • Класс OleDbConnection: можно считать, что этот класс представляет соединение с источником данных OLE DC и содержит свойства, необходимые для подключения к базе данных (данные провайдера OLE DB, имя пользователя и пароль). После соединения в экземпляре класса сохраняются дополнительные метаданные о базе данных.
  • Класс 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.

Рис. 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, поэтому преобразования данных с потерей точности допускаются лишь с явным приведением к заданному типу.

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.

Нетривиальный пример работы с базами данных в 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. За подробностями обращайтесь к специализированной литературе.



Опубликовал admin
22 Авг, Воскресенье 2004г.



Программирование для чайников.