前回は、SQL Server にサンプルデータベースを用意しました。
今回は、ADO.NET のデータプロバイダーを抽象化して扱う方法を見ていきます。
SQL Server だけを使うアプリケーションなら、SqlConnection、SqlCommand、SqlDataReader を直接使えば十分です。
しかし、複数のデータベースに対応したいツールやライブラリでは、具象型に依存しすぎると差し替えが難しくなります。
具象型に直接依存する書き方
SQL Server 専用に書くと、次のようになります。
using Microsoft.Data.SqlClient;
await using SqlConnection connection = new SqlConnection(connectionString);
await connection.OpenAsync();
await using SqlCommand command = connection.CreateCommand();
command.CommandText = "SELECT COUNT(*) FROM Inventory";
int count = (int)await command.ExecuteScalarAsync();
このコードは分かりやすく、SQL Server 固有の機能も使いやすいです。
一方で、SQLite や PostgreSQL に切り替えたい場合、型名や接続文字列、パラメーター表記などを変更する必要があります。
共通インターフェイスで見る
ADO.NET の接続やコマンドは、共通インターフェイスを実装しています。
using System.Data;
using Microsoft.Data.SqlClient;
IDbConnection connection = new SqlConnection(connectionString);
connection.Open();
IDbCommand command = connection.CreateCommand();
command.CommandText = "SELECT COUNT(*) FROM Inventory";
object? result = command.ExecuteScalar();
Console.WriteLine(result);
この形にすると、呼び出し側は IDbConnection や IDbCommand として扱えます。
ただし、インターフェイスだけでは具体的な接続オブジェクトの生成までは抽象化できません。
生成部分では、結局 new SqlConnection(...) が出てきます。
DbProviderFactory の考え方
DbProviderFactory は、接続、コマンド、パラメーターなどを作成するファクトリです。
SQL Server 用のファクトリを使うと、次のように書けます。
using System.Data.Common;
using Microsoft.Data.SqlClient;
DbProviderFactory factory = SqlClientFactory.Instance;
await using DbConnection connection = factory.CreateConnection()
?? throw new InvalidOperationException("接続を作成できません。");
connection.ConnectionString = connectionString;
await connection.OpenAsync();
await using DbCommand command = connection.CreateCommand();
command.CommandText = "SELECT COUNT(*) FROM Inventory";
object? result = await command.ExecuteScalarAsync();
Console.WriteLine(result);
このコードでは、DbConnection、DbCommand という共通の抽象クラスで扱っています。
設定からプロバイダーを切り替える
プロバイダー名と接続文字列を設定へ持たせると、切り替えやすくなります。
{
"Database": {
"Provider": "Microsoft.Data.SqlClient",
"ConnectionString": "Server=localhost,1433;Database=AutoLot;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;"
}
}
ファクトリを選ぶ処理です。
using System.Data.Common;
using Microsoft.Data.SqlClient;
static DbProviderFactory GetFactory(string providerName)
{
return providerName switch
{
"Microsoft.Data.SqlClient" => SqlClientFactory.Instance,
_ => throw new NotSupportedException(
$"未対応のプロバイダーです: {providerName}")
};
}
利用側です。
string providerName = configuration["Database:Provider"]
?? throw new InvalidOperationException("プロバイダー名がありません。");
string connectionString = configuration["Database:ConnectionString"]
?? throw new InvalidOperationException("接続文字列がありません。");
DbProviderFactory factory = GetFactory(providerName);
await using DbConnection connection = factory.CreateConnection()
?? throw new InvalidOperationException("接続を作成できません。");
connection.ConnectionString = connectionString;
await connection.OpenAsync();
この形にすると、アプリケーション本体の多くのコードを共通化できます。
パラメーターもファクトリで作る
コマンドのパラメーターもファクトリから作れます。
await using DbCommand command = connection.CreateCommand();
command.CommandText = """
SELECT Id, Make, Color, PetName
FROM Inventory
WHERE Make = @make
""";
DbParameter parameter = command.CreateParameter();
parameter.ParameterName = "@make";
parameter.Value = "Honda";
command.Parameters.Add(parameter);
await using DbDataReader reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
Console.WriteLine(reader["PetName"]);
}
パラメーター名の表記は、プロバイダーによって違いが出ることがあります。
SQL Server では @name 形式が一般的ですが、すべてのデータベースで完全に同じとは限りません。
抽象化の利点
データプロバイダーを抽象化すると、次の利点があります。
- 複数データベース対応の入口を作れる
- テスト用に別プロバイダーへ差し替えやすい
- ライブラリ側で特定データベースへの依存を減らせる
- 接続・コマンド生成の責務を集約できる
特に、ツールやフレームワーク的なコードでは有効です。
抽象化の欠点
一方で、抽象化には欠点もあります。
- 各データベース固有の機能を使いにくい
- SQL 方言の違いは消えない
- パラメーター表記やデータ型の違いは残る
- コードが少し複雑になる
- 結局、完全な移植性は簡単ではない
「どのデータベースでも同じ SQL が動く」と期待しすぎると危険です。
抽象化できるのは、接続やコマンド生成の形であって、SQL の意味まですべて共通化できるわけではありません。
実務での判断
SQL Server だけを使う業務アプリなら、SqlConnection などの具象型を直接使う方が読みやすいことが多いです。
await using SqlConnection connection = new SqlConnection(connectionString);
一方、複数プロバイダー対応の共通ライブラリや、接続先を切り替える診断ツールでは、DbProviderFactory が役立ちます。
抽象化は目的があって初めて価値を持ちます。
将来使うかもしれない、という理由だけで全コードを抽象化すると、かえって保守しづらくなります。
まとめ
ADO.NET には、プロバイダー固有の具象型と、共通化されたインターフェイス・抽象クラスがあります。
DbProviderFactory を使うと、接続、コマンド、パラメーターの生成をプロバイダーごとに切り替えられます。
ただし、SQL 方言やデータ型の違いまでは完全に隠せません。
次回は、SQL Server を対象に、接続、コマンド、読み取り処理をもう少し実践的に掘り下げます。