visual - C#DBNull y tipos anulables-la forma más limpia de conversión
system dbnull error (8)
Tengo una DataTable, que tiene varias columnas. Algunas de esas columnas son nulables.
DataTable dt; // Value set.
DataRow dr; // Value set.
// dr["A"] is populated from T-SQL column defined as: int NULL
Entonces, ¿cuál es la forma más limpia de conversión de un valor en un DataRow a una variable que se puede anular?
Idealmente, podría hacer algo como:
int? a = dr["A"] as int?;
Editar : Resulta que PUEDE hacer esto, el efecto secundario es que si sus tipos de esquema no son enteros, entonces SIEMPRE va a regresar nulo. La respuesta de Ruben de usar dr.Field<int?>("A")
asegura que las discrepancias de tipos no fallen silenciosamente. Esto, por supuesto, será recogido por pruebas unitarias exhaustivas.
En cambio, generalmente estoy escribiendo algo como:
int? a = dr["A"] != DBNull.Value ? (int)dr["A"] : 0;
Este es un montón de teclas más, pero lo más importante, hay más espacio para que alguien llene algo con una pulsación de tecla incorrecta. Sí, una prueba de unidad lo recogerá, pero prefiero pararlo por completo.
¿Cuál es el patrón más limpio y menos propenso a errores para esta situación?
¿Por qué no usar LINQ? Hace la conversión por ti.
Este es el propósito de la clase DataRowExtensions
en .NET 3.5, que proporciona métodos estáticos Field<T>
y SetField<T>
para el tropezón de datos que SetField<T>
(y que no SetField<T>
) entre los tipos DataRow
y .NET.
int? fld = row.Field<int?>("ColumnA")
establecerá fld
en null
si row["ColumnA"]
contiene DBNull.Value
, a su valor si contiene un entero, y arroja una excepción si contiene algo más. Y en el camino de regreso,
row.SetField("ColumnA", fld);
hace lo mismo al revés: si fld
contiene null
, establece la row["ColumnA"]
en DBNull.Value
y, de lo contrario, lo establece en el valor de fld
.
Hay sobrecargas de Field
y SetField
para todos los tipos de valores que admite DataRow
(incluidos DataRow
tipos que no admiten nulos), por lo que puede usar el mismo mecanismo para obtener y configurar campos independientemente de su tipo de datos.
Lo siguiente funcionaría de forma segura:
Recorte:
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;
}
}
Uso:
SqlDataReaderEx.TryParse(drReader, "MyColumnName", -1);
Métodos de extensión!
Algo como lo siguiente:
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;
}
}
Úselo así:
int? a = dr.GetNullableValue<int>("A");
o
string b = dr.GetValue<string>("B");
El capítulo LINQ to DataSets de LINQ in Action es una buena lectura.
Una cosa que verás es el método de extensión Field<T>
, que se usa de la siguiente manera:
int? x = dr.Field<int?>( "Field" );
O
int y = dr.Field<int?>( "Field" ) ?? 0;
O
var z = dr.Field<int?>( "Field" );
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();
int? a = (int?)dr["A"]
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;
}
Para llamar a la función, podrías escribir
var dt = new DataTable();
dt.Columns.Add("ColumnName");
....
Add rows in Datatable.
....
dt.Rows[0].GetColumnValue("ColumnName);