SqlDataReaderはSQLデータベースから順方向専用の結果セットを読み込みます。 結果をすべて DataSet に読み込むわけではないため、行が到着するたびに処理する場合に適しています。
この記事の例は、名前がSqlConnectionである開いているconnectionがAdventureWorks のサンプル データベースを対象としていることを前提としています。
C#の例は using Microsoft.Data.SqlClient;を必要とします。
SqlDbTypeやCommandBehaviorを使う例もusing System.Data;が必要です。
行の読み取り
基本的なリーダーワークフローは以下の通りです:
-
ExecuteReaderまたはExecuteReaderAsyncでコマンドを実行します。 - 現在の結果について、列の順序番号を決定します。
- 各行ごとに
Readを1回呼び出してください。 - 型付けされたゲッターを呼び出す前に、
IsDBNullでnullable列をチェックしてください。 - 接続を再利用したり、出力パラメーターを読み取ったりする前に、リーダーを破棄してください。
この例は列順序数をキャッシュし、型付けゲッターを使い、nullable 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値に対してNULLを呼び出すとSqlNullValueExceptionがスローされるので、まず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 で実行し、 GetStream、 GetTextReader、または GetXmlReaderを使いましょう。 詳細については、 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を有効にしていると、別々のコマンドが1つの接続で保留結果を持つことはできますが、それでもそれらの操作はスレッド安全ではありません。 詳細については 、複数のアクティブ結果セットをご覧ください。
コマンドに 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を使って複数の結果セットを取得する
1つのコマンドやストアドプロシージャで複数の結果を返すことができます。 現在の結果を完全に処理してから、NextResult または NextResultAsync を呼び出します。
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());
カラム数、名前、順序数、型は結果ごとに変わることがあります。
NextResult が true を返した後、キャッシュされた順序番号を再計算します。
リーダーに対しては、同時に1つの非同期操作のみが保留可能です。 同じリーダーで別の操作を開始する前に、ReadAsync、NextResultAsync、または非同期フィールド操作を待機してください。
DataReader からのスキーマ情報の取得
現在の結果の列に関する型指定付きの説明を確認するには、GetColumnSchema を押してください。 既存のコードで DataTable のスキーマが必要になった場合は、GetSchemaTable に呼び出します:
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 行を返さずに列メタデータをリクエストします。 スキーマ情報は現在の結果セットを記述します。 次の結果に進んだ後に再度リクエストしてください。 クエリやサーバーの権限によっては、一部のベーステーブルメタデータが利用できない場合があります。