В прошлой теме мы рассмотрели, как выполнять команды с помощью метода ExecuteNonOuery()/ExecuteNonOueryAsync()
, однако если мы хотим считывать данные,
которые хранятся в таблице, то нам потребуется другой метод - ExecuteReader() / ExecuteReaderAsync().
Этот метод возвращает объект SqlDataReader, который используется для чтения данных.
Основные свойства класса SqlDataReader, которые мы можем использовать при получении данных:
FieldCount: количество столбцов в текущей строке
HasRows: указывает, содержит ли SqlDataReader как минимум одну строку
IsClosed: возвращает значение типа bool
, которое указывает, закрыт ли данный экземпляр SqlDataReader
Item[Int32]: возвращает значение столбца, номер которого передается в квадратных скобках
Item[String]: возвращает значение столбца, название которого передается в квадратных скобках
Среди методов класса SqlDataReader, которые применяются при работе с данным классом, следует выделить следующие:
Close()/CloseAsync(): закрывает объект SqlDataReader
GetValue(Int32): возвращает значение столбца, номер которого передается в качестве параметра (нумерация начинается с нуля)
Read(): считывает следующую строку в полученном наборе
ReadAsync(): асинхронно считывает следующую строку из полученного набора, одна из версий данного метода в качестве параметра принимает токен отмены CancellationToken
Например, получим все данные из таблицы Users, созданной в прошлой теме, и выведем их на консоль:
using Microsoft.Data.SqlClient; using System; using System.Threading.Tasks; namespace HelloApp { class Program { static async Task Main(string[] args) { string connectionString = "Server=(localdb)\\mssqllocaldb;Database=adonetdb;Trusted_Connection=True;"; string sqlExpression = "SELECT * FROM Users"; using (SqlConnection connection = new SqlConnection(connectionString)) { await connection.OpenAsync(); SqlCommand command = new SqlCommand(sqlExpression, connection); SqlDataReader reader = await command.ExecuteReaderAsync(); if (reader.HasRows) // если есть данные { // выводим названия столбцов string columnName1 = reader.GetName(0); string columnName2 = reader.GetName(1); string columnName3 = reader.GetName(2); Console.WriteLine($"{columnName1}\t{columnName3}\t{columnName2}"); while (await reader.ReadAsync()) // построчно считываем данные { object id = reader.GetValue(0); object name = reader.GetValue(2); object age = reader.GetValue(1); Console.WriteLine($"{id} \t{name} \t{age}") ; } } await reader.CloseAsync(); } Console.Read(); } } }
Консольный вывод:
Id Name Age 2 Alice 32 3 Bob 28 4 Alex 41
Для выборки данных из БД используется sql-выражение SELECT. В данном случае мы выбираем все столбцы всех строк таблицы.
Получив при выполнении запроса объект SqlDataReader
, мы можем считать все полученные данные.
Но вначале мы проверяем, а есть ли вообще данные с помощью свойства HasRows
. Если данные есть, то выводим заголовки таблицы с помощью
методов reader.GetName()
. Причем мы получаем столбцы в выборке именно в том порядке, в котором они определены в таблице.
CREATE TABLE Users (Id INT PRIMARY KEY IDENTITY, Age INT NOT NULL, Name NVARCHAR(20) NOT NULL)
Так, третим в таблице идет столбец "Name", то чтобы получить его столбец применяется метод GetName(2)
(так как нумерация столбцов идет с нуля).
Далее считываем сами данные. С помощью метода reader.ReadAsync()
ридер переходит к следующей строке и возвращает булевое значение,
которое указывает, есть ли данные для считывания.
В цикле while (reader.Read())
в порядке следования столбов получаем данные с помощью метода GetValue()
,
который возвращает данные в виде объекта типа object
. Например, столбец Id идет первым и представляет целое число, поэтому для его получения
применяется метод reader.GetValue(0)
. А столбец Age идет вторым, поэтому его значения получаем с помощью reader.GetValue(1)
.
После завершения работы с SqlDataReader надо его закрыть методом Close()/CloseAsync()
. И пока один SqlDataReader не закрыт, другой объект
SqlDataReader для одного и того же подключения мы использовать не сможем.
В качестве альтернативы мы бы могли облачить SqlDataReader в конструкцию using
:
using Microsoft.Data.SqlClient; using System; using System.Threading.Tasks; namespace HelloApp { class Program { static async Task Main(string[] args) { string connectionString = "Server=(localdb)\\mssqllocaldb;Database=adonetdb;Trusted_Connection=True;"; string sqlExpression = "SELECT * FROM Users"; using (SqlConnection connection = new SqlConnection(connectionString)) { await connection.OpenAsync(); SqlCommand command = new SqlCommand(sqlExpression, connection); using (SqlDataReader reader = await command.ExecuteReaderAsync()) { if (reader.HasRows) // если есть данные { // выводим названия столбцов string columnName1 = reader.GetName(0); string columnName2 = reader.GetName(1); string columnName3 = reader.GetName(2); Console.WriteLine($"{columnName1}\t{columnName3}\t{columnName2}"); while (await reader.ReadAsync()) // построчно считываем данные { object id = reader.GetValue(0); object name = reader.GetValue(2); object age = reader.GetValue(1); Console.WriteLine($"{id} \t{name} \t{age}") ; } } } } Console.Read(); } } }
В примере выш используется асинхронное считывание, но также можно использовать синхронное считывание:
while (reader.Read()) // построчно считываем данные { object id = reader.GetValue(0); object name = reader.GetValue(2); object age = reader.GetValue(1); Console.WriteLine($"{id} \t{name} \t{age}") ; }
Выше для извлечения данных из строки использовался метод GetValue()
, в который передавался номер столбца. В
качестве альтернативы мы могли бы обращаться к данным через индексатор ридера:
while (await reader.ReadAsync()) { object id = reader[0]; object name = reader[2]; object age = reader[1]; Console.WriteLine($"{id} \t{name} \t{age}"); }
Однако, мы можем сомневаться в порядке следования столбцов, и в этом случае можно передавать названия столбцов:
while (await reader.ReadAsync()) { object id = reader["id"]; object name = reader["name"]; object age = reader["age"]; Console.WriteLine($"{id} \t{name} \t{age}"); }
В этом случае результат будет аналогичным.