C# DBNull и nullable Types - чистейшая форма конвертации
У меня есть DataTable, который имеет ряд столбцов. Некоторые из этих столбцов обнуляются.
DataTable dt; // Value set.
DataRow dr; // Value set.
// dr["A"] is populated from T-SQL column defined as: int NULL
Что же тогда является самой чистой формой преобразования значения в DataRow в переменную, допускающую значение NULL?
В идеале я мог бы сделать что-то вроде:
int? a = dr["A"] as int?;
Редактировать: Оказывается, вы МОЖЕТЕ сделать это, побочным эффектом является то, что если ваши типы схем не являются целочисленными, то это ВСЕГДА будет возвращать ноль. Ответ Рубена об использованииdr.Field<int?>("A")
гарантирует, что несоответствия типов не произойдут без ошибок. Это, конечно, будет подхвачено тщательными юнит-тестами.
Вместо этого я обычно набираю что-то вроде:
int? a = dr["A"] != DBNull.Value ? (int)dr["A"] : 0;
Это намного больше нажатий клавиш, но что более важно, есть больше места для того, чтобы кто-то набил что-то неправильным нажатием клавиши. Да, юнит тест подхватит это, но я бы лучше вообще его остановил.
Какой самый чистый, наименее подверженный ошибкам шаблон для этой ситуации.
8 ответов
Раздел LINQ to DataSets в LINQ в действии хорошо читается.
Одна вещь, которую вы увидите, это Field<T>
метод расширения, который используется следующим образом:
int? x = dr.Field<int?>( "Field" );
Или же
int y = dr.Field<int?>( "Field" ) ?? 0;
Или же
var z = dr.Field<int?>( "Field" );
Это цель DataRowExtensions
класс в.NET 3.5, который обеспечивает статический Field<T>
а также SetField<T>
методы обнуляющих (и не обнуляемых) данных между циклами DataRow
и.NET типы.
int? fld = row.Field<int?>("ColumnA")
установит fld
в null
если row["ColumnA"]
содержит 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
содержит null
, it sets row["ColumnA"]
в DBNull.Value
, and otherwise sets it to the value of fld
,
There are overloads of Field
а также 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.
Почему бы не использовать LINQ? Это делает преобразование для вас.
Следующее будет работать, безопасно:
Надрез:
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;
}
}
Использование:
SqlDataReaderEx.TryParse(drReader, "MyColumnName", -1);
Методы расширения!
Что-то вроде следующего:
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;
}
}
Используйте это так:
int? a = dr.GetNullableValue<int>("A");
или же
string b = dr.GetValue<string>("B");
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;
}
Для вызова функции вы можете написать
var dt = new DataTable();
dt.Columns.Add("ColumnName");
....
Add rows in Datatable.
....
dt.Rows[0].GetColumnValue("ColumnName);
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();