C#でDBのデータを取得する際に厄介なのがDBNull。。
DBNullはNullとは別物のため、Null判定できません。
故に三項演算子などを使ってDBNull回避する必要があります。
そこでDBNullをうまく回避しつつ、同時にキャストしてくれる関数を作れないか?と考えたところ。
私の中での最適解が生まれたので書いていきます。
public static T Convert<T>(object obj)
{
if (obj != DBNull.Value && obj != null)
{
return (T) obj;
}
return default;
}</code></pre></div>
object型は何でも受け取ることができます。
なので一度Object形で受け取り、DBNullとNullでないことをチェックします。
DBNullでもNullでもなければ、指定の型にキャストします。
Nullだった場合Defaultを返します。
これでDBNullをきれいに回避することができるのではないでしょうか??
この関数の使い方は下記のようになります。(SqlDataReaderで書いていきますが、DataRowでも使えると思います。)
using (var dataReader = command.ExecuteReader())
{
if (dataReader.HasRows)
{
while (dataReader.Read())
{
var username = Convert<string>(reader["UserName"]);
var userId = Convert<string>(reader["UserId"]);
var userpass = Convert<string>(reader["UserPass"]);
var roleId = Convert<int>(reader["RoleId"]);
}
}
}
キャストしたい型を指定して、引数にreaderのItemを投げればうまくできますね!
拡張メソッドを使えば下記のようにできますが、すべての型で拡張されてしまうので、推奨はしません。
拡張メソッドで書いてみる
💡 DataAdapter.Update で DBNull で例外が出るのは保守の現場あるある。 C# DataAdapter.Update() で DBNull 例外が出た時の最短対処 で同じ系統のハマりを潰してます。
public static class ObjectHelper
{
public static T ReplaceDBNull<T>(this object obj)
{
if (obj != DBNull.Value && obj != null)
{
return (T) obj;
}
return default;
}
}</code></pre></div>
こんな感じで書くと以下のような形で使えます。
using (var dataReader = command.ExecuteReader())
{
if (dataReader.HasRows)
{
while (dataReader.Read())
{
var username = reader["UserName"].ReplaceDBNull<string>();
var userId = reader["UserId"].ReplaceDBNull<string>();
var userpass = reader["UserPass"].ReplaceDBNull<string>();
var roleId = reader["RoleId"].ReplaceDBNull<int>();
}
}
}
この方法は推奨はしません。
何故ならObject型はすべての型が継承している型になっています。
それすなわち全ての型にReplaceDBNullが実装されるということになります。
個人開発ならまだいいかもしれませんが、みんなで使う際は避けたほうがいいかもしれませんね。
目次💡 補足: 業務系の現場でよくハマるパターン
俺もこの DBNull のチェック処理、客先で何度も書いてきましたが、3つの罠は今でも油断すると踏みます。
① DBNull と null の混在で if 文が機能しない
if (row["col"] == null) は DBNull で来た値には false 判定なので、 row["col"] is DBNull or row.IsNull("col") を使う必要があります。レガシーコードで != null チェックが効かないバグの定番原因。 Linq で Null 回避する書き方とパフォーマンス でも同じ罠を扱ってます。
② 値型 (int, DateTime) で DBNull キャスト失敗
(int)row["col"] で DBNull が来ると InvalidCastException。Null 許容型で受けるか、 row.Field("col") の Field 拡張メソッドを使うのが安全です。今回の拡張メソッドはまさにこれを楽にする実装ですね。
③ DataRow["col"] の文字列キー指定でタイポ → 実行時例外
列名のスペルミスは IDE で気づけない。実行時に IndexOutOfRangeException で初めて発覚する。型付き DataSet を使うか、定数化するのが業務系での自衛策。DataGridView × DataTable でバインディングする時もこの罠に注意。
❓ よくある質問
Q1. DBNull と null の違いは?
A. DBNull は「DB の NULL を C# 側で表すための特殊値」、null は「.NET の参照型がない状態」。DataTable から取り出した時の値が DBNull、変換後に保持する変数が null、と思うと頭が整理されます。 DataAdapter で Fill と Update でも DBNull の取り回しを扱ってます。
Q2. 値型 (int? DateTime?) でのキャストはどう書く?
A. row.Field("col") が最短。Null 許容型を使えば DBNull が null に自動変換される。Field 拡張メソッドが業務系の DataTable 操作で最強の友です。
Q3. Linq で DBNull を含む列を扱うには?
A. dt.AsEnumerable().Where(r => !r.IsNull("col")) で DBNull 行を除外、または r.Field("col") ?? 0 でデフォルト値に倒す。 DataTable × Linq の3パターン で具体的なコード例を出してます。
Q4. SQL Server の NULL 列を判定するには?
A. SQL 側で ISNULL(col, デフォルト) か COALESCE で潰すのが定石です。C# 側でチェックするより SQL 側で潰す方が抜け漏れが減る。レガシー保守だと両側でチェックしてる二重防御パターンもよく見ます。
Q5. EF Core ならどうハンドリング?
A. EF Core はプロパティ型を nullable にしておけば自動で DBNull → null に変換してくれます。DBNull を意識する必要がほぼないのが嬉しい。レガシー DataAdapter からの移行で「DBNull 地獄から解放される」のは EF Core のメリットの1つ。
📚 関連記事
- C# DataAdapter.Update() で DBNull 例外が出た時の最短対処 — DBNull 系の代表的な本番障害
- C# の Linq で Null 回避する書き方とパフォーマンス — Linq での null/DBNull の扱い
- C# DataTable を Linq でフィルタ・GroupBy・分割する3パターン — DBNull を含む DataTable の Linq 操作
- 【C#】DataAdapterを使ってFillとUpdateしてみる — DataTable に DBNull を含むデータが入る経路
- 【C#】DataGridViewにDataTable反映したり変換して取得したりする — DBNull を含む DataTable をバインドする時の挙動


コメント