C# DataRowの値を取り出すときにDBNullでエラーを出さない。

DataRowから値を読み込むとき、 たとえば、

DataTable dt = new DataTable();
DataColumn dc_id = new DataColumn("id", typeof(UInt64));
dt.Columns.Add(dc_id);
DataColumn dc_name = new DataColumn("name", typeof(String));
dt.Columns.Add(dc_name);
DataColumn dc_chk = new DataColumn("chk", typeof(Boolean));
dt.Columns.Add(dc_chk);

DataRow dr = dt.NewRow();

と適当にDataRowを用意して、

UInt64 id = (UInt64)dr["id"];
String name = (String)dr["name"];
Boolean chk = (Boolean)dr["chk"];

と読み取ろうとすると、
「型 'System.InvalidCastException' のハンドルされていない例外が mscorlib.dll で発生しました」
「追加情報:オブジェクトを DBNull から他のタイプにキャストすることはできません。」
と叱られます。

これは、dr["id"]などのDataRowの値がNullだからです。

そこで、dr["id"]の値がNullかどうかを調べる必要があるわけですが、if (dr["id"]!=null) とやっても、dr["id"]そのものはNullではないのでうまく行きません。

dr["id"]の値がNullかどうかは、if (!dr.IsNull("id")) とか、if (dr!=DBNull.Value) のようにするといいわけですが、IF文を大量に並べるのはちょっと見苦しいですよね。

DBNull.Value.Equalsと三項演算子で処理する

そこで、DBNull.Value.Equalsを使って、DataRowのカラムの値がnullなら、三項演算子で任意の値を入れるようにします。

UInt64 id = DBNull.Value.Equals(dr["id"]) ? 0 : (UInt64)dr["id"];
String name = DBNull.Value.Equals(dr["name"]) ? "" : (String)dr["name"];
Boolean chk = DBNull.Value.Equals(dr["chk"]) ? false : (Boolean)dr["name"];

とすると、DBNullのときでもエラーが出ません。 DBNull.Value.Equals は、内容DBNullかどうかをチェックしてDBNullならTrueを返します。

DataRow.Fieldとnull 合体演算子で処理する

でも、あんまりスマートな書き方でないので、調べたら .Net Framework3.5からこんな書き方ができるようになっていました。

UInt64 id = dr.Field<UInt64?>("id") ?? 0;
String name = dr.Field<String>("name") ?? "";
Boolean chk = dr.Field<Boolean?>("chk") ?? false;

dr.Field<UInt64?>("id") で、dr["id"]の中身を取り出してくれます。
UInt64? の ? は、Nullable型とすることを表して、Null非許容型に対してNullを入れられるようにします。String はもともとNull許容型なので、?はつけません。
?? はNullかどうかを判定する演算子で、Nullなら、別の値をいれることができます。

なので、 UInt64 id = dr.Field<UInt64?>("id")??0; は、dr["id"]の内容がDBNullならnullとし、その場合0をidに入れる、ということになります。

さらに、defaultで初期値を取得すれば、書き方を定型化できます。

UInt64 id = dr.Field<UInt64?>("id") ?? default(UInt64);
String name = dr.Field<String>("name") ?? default(String);
Boolean chk = dr.Field<Boolean?>("chk") ?? default(Boolean);

 

DataRowの値を取得する関数を作る

いちいちFieldを使うのも面倒なので、個人的には次のように関数化してつかってます。

public static Type ConvertDataRow<Type>(DataRow dr, String columnName)
{
       Type ret = (dr.IsNull(columnName)) ? default(Type) : dr.Field<Type>(columnName);
       return ret;
}

こんな感じで使います。

UInt64 id = ConvertDataRow<UInt64>(dr, "id");
String name = ConvertDataRow<String>(dr, "name");
Boolean chk = ConvertDataRow<Boolean>(dr, "chk");

 

ちなみに、MySQLのtinyint型の場合は、byteとして取り込まれるので、つぎのようにして取得します。

int status = Common.Datas.ConvertDataRow<byte>(dr2, "status");

 

  • このエントリーをはてなブックマークに追加