SqlDataReader는 SQL 데이터베이스에서 순방향 전용 결과 시퀀스를 읽습니다. 전체 결과를 DataSet에 로드하지 않기 때문에, 행이 도착하는 대로 처리하기에 적합합니다.
이 문서의 예제에서는 이름이 connection인 열려 있는 SqlConnection가 AdventureWorks 샘플 데이터베이스를 대상으로 한다고 가정합니다.
C# 예제에는 using Microsoft.Data.SqlClient;가 필요합니다.
SqlDbType 또는 CommandBehavior를 사용하는 예시에는 using System.Data;도 또한 필요합니다.
행 읽기
기본 리더 워크플로우는 다음과 같습니다:
-
ExecuteReaderAsync또는ExecuteReader로 명령을 실행하세요. - 현재 결과에 대해 열 서수를 확인합니다.
- 각 행마다
Read을 한 번 호출하세요. - 타입이 지정된 게터를 호출하기 전에 nullable 열
IsDBNull을 확인하세요. - 연결을 다시 사용하거나 출력 매개변수를 읽기 전에 리더를 해제하세요.
이 예시는 열의 순서수를 캐시하고, 타입이 지정된 게터를 사용하며, null 허용 Color 열을 확인합니다:
using var command = new SqlCommand("""
SELECT ProductID, Name, Color
FROM Production.Product
WHERE ProductSubcategoryID = @subcategoryId
ORDER BY ProductID;
""", connection);
command.Parameters.Add("@subcategoryId", SqlDbType.Int).Value = 2;
using SqlDataReader reader = command.ExecuteReader();
int idOrdinal = reader.GetOrdinal("ProductID");
int nameOrdinal = reader.GetOrdinal("Name");
int colorOrdinal = reader.GetOrdinal("Color");
while (reader.Read())
{
int id = reader.GetInt32(idOrdinal);
string name = reader.GetString(nameOrdinal);
string? color = reader.IsDBNull(colorOrdinal)
? null
: reader.GetString(colorOrdinal);
Console.WriteLine($"{id} | {name} | {color ?? "(no color)"}");
}
GetString 값에서 SqlNullValueException을 호출하면 NULL이(가) 발생하므로, 먼저 IsDBNull을(를) 확인해 보세요.
HasRows 현재 결과에 행이 포함되어 있는지 보고하지만, 보통은 Read를 직접 호출할 수 있습니다. 비어 있는 결과의 경우, 첫 Read 호출은 false를 반환합니다:
using var command = new SqlCommand("""
SELECT ProductID
FROM Production.Product
WHERE ProductID = @productId;
""", connection);
command.Parameters.Add("@productId", SqlDbType.Int).Value = -1;
using SqlDataReader reader = command.ExecuteReader();
Console.WriteLine($"HasRows: {reader.HasRows}");
Console.WriteLine($"First Read: {reader.Read()}");
큰 값의 경우 CommandBehavior.SequentialAccess로 실행하고 GetTextReader, GetXmlReader 또는 GetStream를 사용합니다. 자세한 내용은 SqlClient 스트리밍 지원을 참조하세요.
DataReader 닫기
처리가 끝나면 리더를 즉시 폐기하세요.
await using 또는 using 문은 행 처리 중 예외가 발생하더라도 이를 종료합니다.
출력 매개변수, 입출력 매개변수, 저장 프로시저 반환 값은 리더가 닫히기 전까지는 최종 확정되지 않습니다:
using var command = new SqlCommand("""
SELECT TOP (5) ProductID, Name
FROM Production.Product
ORDER BY ProductID;
SET @count = @@ROWCOUNT;
""", connection);
SqlParameter count = command.Parameters.Add("@count", SqlDbType.Int);
count.Direction = ParameterDirection.Output;
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader.GetString(1));
}
}
Console.WriteLine($"Rows returned: {count.Value}");
다중 활성 결과 집합(MARS)이 없으면, 열린 리더는 연결을 바쁘게 유지하여 다른 명령어가 해당 연결에서 실행될 수 없습니다. MARS가 활성화되면 별도의 명령어가 한 연결에서 대기 결과를 가질 수 있지만, 그 작업들은 여전히 스레드 안전하지 않습니다. 자세한 내용은 다중 활성 결과 집합을 참조하세요.
전달된 명령이 CommandBehavior.CloseConnection인 경우 리더를 닫으면 연결도 종료됩니다. 그렇지 않으면 연결을 열었던 코드가 해당 연결을 해제할 책임을 계속 집니다.
영향받은 행 수 읽기
RecordsAffected는 명령에서 INSERT, UPDATE, DELETE 문에 의해 변경된 행을 보고합니다. 리더를 닫은 후에 읽으세요. 이 예시는 업데이트를 롤백합니다:
using SqlTransaction transaction = connection.BeginTransaction();
using var command = new SqlCommand("""
UPDATE Production.Product
SET ModifiedDate = ModifiedDate
WHERE ProductID IN (706, 707);
SELECT ProductID, Name
FROM Production.Product
WHERE ProductID IN (706, 707)
ORDER BY ProductID;
""", connection, transaction);
SqlDataReader reader = command.ExecuteReader();
using (reader)
{
while (reader.Read())
{
Console.WriteLine($"{reader.GetInt32(0)} | {reader.GetString(1)}");
}
}
Console.WriteLine($"RecordsAffected: {reader.RecordsAffected}");
transaction.Rollback();
SELECT 전용 명령의 경우, RecordsAffected는 -1입니다.
SET NOCOUNT ON 카운트를 억제하여 값도 -1로 유지됩니다.
NextResult를 사용하여 여러 결과 집합을 검색하세요
하나의 명령어나 저장 프로시저로 여러 결과를 반환할 수 있습니다. 현재 결과를 완전히 처리한 후 NextResultAsync 또는 NextResult를 호출합니다:
using var command = new SqlCommand("""
SELECT TOP (3) ProductID, Name
FROM Production.Product
ORDER BY ProductID;
SELECT COUNT(*) AS ProductCount
FROM Production.Product;
""", connection);
using SqlDataReader reader = command.ExecuteReader();
int result = 1;
do
{
Console.WriteLine($"Result {result++}: {reader.FieldCount} column(s)");
while (reader.Read())
{
var values = new object[reader.FieldCount];
reader.GetValues(values);
Console.WriteLine(string.Join(" | ", values));
}
}
while (reader.NextResult());
각 결과마다 열의 수, 이름, 순서, 유형이 바뀔 수 있습니다.
true가 NextResult를 반환한 후 캐시된 서수를 재계산합니다.
한 번에 리더 하나에는 비동기 작업 하나만 보류될 수 있습니다.
ReadAsync, NextResultAsync 또는 비동기 필드 연산을 대기한 후 같은 리더기에서 다른 연산을 시작하세요.
DataReader에서 스키마 정보 가져오기
현재 결과의 열에 대한 강력한 형식의 설명을 얻으려면 GetColumnSchema를 호출하세요. 기존 코드에 스키마가 GetSchemaTable 필요할 때 DataTable를 호출합니다:
using var command = new SqlCommand("""
SELECT ProductID, Name, Color
FROM Production.Product;
""", connection);
using SqlDataReader reader = command.ExecuteReader(
CommandBehavior.SchemaOnly);
foreach (System.Data.Common.DbColumn column in reader.GetColumnSchema())
{
Console.WriteLine(
$"{column.ColumnOrdinal}: {column.ColumnName} " +
$"({column.DataTypeName}), nullable={column.AllowDBNull}");
}
CommandBehavior.SchemaOnly 행 없이 열의 메타데이터를 요청합니다. 스키마 정보는 현재 결과 집합을 설명합니다. 다음 결과로 넘어간 후 다시 요청하세요. 쿼리와 서버 권한에 따라 일부 베이스 테이블 메타데이터가 제공되지 않을 수 있습니다.