问题
I have a DataTable, which has a number of columns. Some of those columns are nullable.
DataTable dt; // Value set.
DataRow dr; // Value set.
// dr["A"] is populated from T-SQL column defined as: int NULL
What, then, is the cleanest form of converting from a value in a DataRow, to a nullable variable.
Ideally, I would be able to do something like:
int? a = dr["A"] as int?;
Edit: Turns out you CAN do this, the side effect being that if your Schema types arn't ints, then this is ALWAYS going to return null. The answer by Ruben of using dr.Field<int?>("A")
ensures type mismatches don't silently fail. This, of course, will be picked up by thorough unit tests.
Instead I'm usually typing something along the lines of:
int? a = dr["A"] != DBNull.Value ? (int)dr["A"] : 0;
This is a bunch more keystrokes, but more importantly, there's more room for someone to stuff something up with a wrong keystroke. Yes, a Unit Test will pick this up, but I'd rather stop it altogether.
What is the cleanest, least error-prone pattern for this situation.
回答1:
The LINQ to DataSets chapter of LINQ in Action is a good read.
One thing you'll see is the Field<T>
extension method, which is used as follows:-
int? x = dr.Field<int?>( "Field" );
Or
int y = dr.Field<int?>( "Field" ) ?? 0;
Or
var z = dr.Field<int?>( "Field" );
回答2:
This is the purpose of the DataRowExtensions
class in .NET 3.5, which provides static Field<T>
and SetField<T>
methods for round-tripping nullable (and non-nullable) data between the DataRow
and .NET types.
int? fld = row.Field<int?>("ColumnA")
will set fld
to null
if row["ColumnA"]
contains DBNull.Value
, to its value if it contains an integer, and throw an exception if it contains anything else. And on the way back,
row.SetField("ColumnA", fld);
does the same thing in reverse: if fld
contains null
, it sets row["ColumnA"]
to DBNull.Value
, and otherwise sets it to the value of fld
.
There are overloads of Field
and SetField
for all of the value types that DataRow
supports (including non-nullable types), so you can use the same mechanism for getting and setting fields irrespective their data type.
回答3:
int? a = (int?)dr["A"]
回答4:
Why not use LINQ? It does the conversion for you.
回答5:
Following would work, safely:
Snip:
public static class SqlDataReaderEx
{
public static int TryParse(SqlDataReader drReader, string strColumn, int nDefault)
{
int nOrdinal = drReader.GetOrdinal(strColumn);
if (!drReader.IsDbNull(nOrdinal))
return drReader.GetInt32(nOrdinal);
else
return nDefault;
}
}
Usage:
SqlDataReaderEx.TryParse(drReader, "MyColumnName", -1);
回答6:
Extension methods!
Something like the following:
public static class DataRowExtensions
{
public static Nullable<T> GetNullableValue<T>(this DataRow row, string columnName)
where T : struct
{
object value = row[columnName];
if (Convert.IsDBNull(value))
return null;
return (Nullable<T>)value;
}
public static T GetValue<T>(this DataRow row, string columnName)
where T : class
{
object value = row[columnName];
if (Convert.IsDBNull(value))
return null;
return (T)value;
}
}
Use it like so:
int? a = dr.GetNullableValue<int>("A");
or
string b = dr.GetValue<string>("B");
回答7:
public static object GetColumnValue(this DataRow row, string columnName)
{
if (row.Table.Columns.Contains(columnName))
{
if (row[columnName] == DBNull.Value)
{
if (row.Table.Columns[columnName].DataType.IsValueType)
{
return Activator.CreateInstance(row.Table.Columns[columnName].DataType);
}
else
{
return null;
}
}
else
{
return row[columnName];
}
}
return null;
}
To call the function you could write
var dt = new DataTable();
dt.Columns.Add("ColumnName");
....
Add rows in Datatable.
....
dt.Rows[0].GetColumnValue("ColumnName);
回答8:
Chart.data = new List < int ?> ();
Chart.data = (from DataRow DR in _dtChartData.Rows
select(int ? )((DR[_ColumnName] == DBNull.Value) ? (int ? ) null : (int ? ) DR[_ColumnName])).ToList();
来源:https://stackoverflow.com/questions/1706405/c-sharp-dbnull-and-nullable-types-cleanest-form-of-conversion