I have a class named project with the following data members.
class Project
{
private int Record_Num;
private int GCD_ID;
private string Project_Desc;
private string Proponent_Name;
private string Station_ID;
private string OpCentre;
private string Sector_ID;
private string PLZone;
private string Feeder_ID;
private string DxTx_ID;
private string OpControl_ID;
private string Type_ID;
private string ConnKV_ID;
private string Status_ID;
private double MW;
private string Subject;
private int Ip_Num;
private int H1N_ID;
private int NOMS_Slip_Num;
private DateTime NMS_Updated;
private DateTime Received_Date;
private Nullable<DateTime> Actual_IS_Date;
private string Scheduled_IS_Date;
private string UP_Station_ID;
private string UP_Feeder_ID;
private string HV_Circuit;
}
i query the database and retrieve the values through a data table which assigns the value to the project object like this.
for (int prjIdx = 0; prjIdx < dt.Rows.Count; prjIdx++)
{
newProject = new Project(Convert.ToInt32(dt.Rows[prjIdx]["RecordNum"].ToString()), Convert.ToInt32(dt.Rows[prjIdx]["GCDID"].ToString()),
dt.Rows[prjIdx]["ProjectDesc"].ToString(), dt.Rows[prjIdx]["ProponentName"].ToString(),
dt.Rows[prjIdx]["StationName"].ToString(), dt.Rows[prjIdx]["OpCentre"].ToString(),
dt.Rows[prjIdx]["SectorName"].ToString(), dt.Rows[prjIdx]["PLZone"].ToString(),
dt.Rows[prjIdx]["FeederDesc"].ToString(), dt.Rows[prjIdx]["DxTx"].ToString(),
dt.Rows[prjIdx]["OpControl"].ToString(), dt.Rows[prjIdx]["Type"].ToString(),
dt.Rows[prjIdx]["ConnectionKV"].ToString(), dt.Rows[prjIdx]["Status"].ToString(),
Convert.ToDouble(dt.Rows[prjIdx]["MW"]), dt.Rows[prjIdx]["Subject"].ToString(),
Convert.ToInt32(dt.Rows[prjIdx]["IpNum"]), Convert.ToInt32(dt.Rows[prjIdx]["H1NID"]),
Convert.ToInt32(dt.Rows[prjIdx]["NomsSlipNum"]),Convert.ToDateTime(dt.Rows[prjIdx]["NmsUpdated"]),
Convert.ToDateTime(dt.Rows[prjIdx]["ReceivedDate"]),Convert.ToDateTime(dt.Rows[prjIdx]["ActualIsDate"]),
dt.Rows[prjIdx]["ScheduledIsDate"].ToString(),dt.Rows[prjIdx]["UpStation"].ToString(),dt.Rows[prjIdx]["UpFeeder"].ToString(),
dt.Rows[prjIdx]["HVCircuit"].ToString());
newProject.record_num = Convert.ToInt32(dt.Rows[prjIdx]["RecordNum"]);
projList.Add(newProject);
}
now my problem is, all the date time values retrieved from the database can be null.so if it encounters a null value, it fails to convert it and hence it cannot be assigned in to the object. thus it give me an error
how do i tackle the proble.
should i change the date time variable to string data type. but thats a lame solution. please help//
DateTime is a value-type, you could use the nullable datetime (DateTime?)
MSDN Ref on Nullable Structure
[...]
private int NOMS_Slip_Num;
private DateTime? NMS_Updated;
private DateTime? Received_Date;
private Nullable<DateTime> Actual_IS_Date;
[...]
Doing so will require changes in how you retrieve the value. One way to do so, would be to implement a null or DBNull check, and set the value of nullable datetimes in the Project instanciation using a ternary operator.
public static class Extensions
{
public static bool IsNull(this object o)
{
if (o == null || DBNull.Value.Equals(o))
return true;
else
return false;
}
}
and use it like this in Project's instanciation :
var receivedDate = dt.Rows[prjIdx]["ReceivedDate"];
var actualDate = dt.Rows[prjIdx]["ActualIsDate"];
newProject = new Project([...],
receivedDate.IsNull() ? null : Convert.ToDateTime(receivedDate),
actualDate.IsNull() ? null : Convert.ToDateTime(actualDate),
[...]);
As everyone has already stated you can use Nullable Types in your Model. As for getting them out of the row and checking for DBNull, MS was smart enough to think of this already, and provided a set of extension methods to do just that.
DataRowExtensions.Field Method
...
dt.Rows[prjIdx].Field<Int32?>("IpNum");
...
As long as your underlying type is in fact an int, then this will do the trick.
The Field method does not perform type
conversions. If type conversion is
required, you should first obtain the
column value by using the Field
method. The column value should then
be converted to another type.
Change to nullable DateTime.
I like to keep this utility class around for dealing with DBNull values
public static class DBNullConvert
{
public static T To<T>(object value, T defaultValue)
{
T cast;
try
{
cast = value == DBNull.Value ? defaultValue : (T)value;
}
catch
{
throw new ArgumentException(string.Format("Argument of type {0} cannot be cast to type {1}", value.GetType(), typeof(T)), "value");
}
return cast;
}
public static T To<T>(object value)
{
return To(value, default(T));
}
public static T? ToNullable<T>(object value) where T : struct
{
T? cast;
try
{
cast = value == DBNull.Value ? null : (T?)value;
}
catch
{
throw new ArgumentException(string.Format("Argument of type {0} cannot be cast to type {1}", value.GetType(), typeof(T?)), "value");
}
return cast;
}
public static string ToString(object value)
{
return To(value, String.Empty);
}
}
Then you can use it like this:
DBNullConvert.ToNullable<DateTime>(dt.Rows[prjIdx]["ReceivedDate"])
which will return null if the field is DBNull.Value or the datetime stored in the field if there is one.
As Dynamy stated you can use the
DateTime? which is a Nullable type of date.
Nullable Type C#
N.
Related
I'm using a SQLdatareader to build POCOs from a database. The code works except when it encounters a null value in the database. For example, if the FirstName column in the database contains a null value, an exception is thrown.
employee.FirstName = sqlreader.GetString(indexFirstName);
What is the best way to handle null values in this situation?
You need to check for IsDBNull:
if(!SqlReader.IsDBNull(indexFirstName))
{
employee.FirstName = sqlreader.GetString(indexFirstName);
}
That's your only reliable way to detect and handle this situation.
I wrapped those things into extension methods and tend to return a default value if the column is indeed null:
public static string SafeGetString(this SqlDataReader reader, int colIndex)
{
if(!reader.IsDBNull(colIndex))
return reader.GetString(colIndex);
return string.Empty;
}
Now you can call it like this:
employee.FirstName = SqlReader.SafeGetString(indexFirstName);
and you'll never have to worry about an exception or a null value again.
You should use the as operator combined with the ?? operator for default values. Value types will need to be read as nullable and given a default.
employee.FirstName = sqlreader[indexFirstName] as string;
employee.Age = sqlreader[indexAge] as int? ?? default(int);
The as operator handles the casting including the check for DBNull.
employee.FirstName = sqlreader[indexFirstName] as string;
For integers, if you cast to a nullable int, you can use GetValueOrDefault()
employee.Age = (sqlreader[indexAge] as int?).GetValueOrDefault();
or the null-coalescing operator (??).
employee.Age = (sqlreader[indexAge] as int?) ?? 0;
IsDbNull(int) is usually much slower than using methods like GetSqlDateTime and then comparing to DBNull.Value. Try these extension methods for SqlDataReader.
public static T Def<T>(this SqlDataReader r, int ord)
{
var t = r.GetSqlValue(ord);
if (t == DBNull.Value) return default(T);
return ((INullable)t).IsNull ? default(T) : (T)t;
}
public static T? Val<T>(this SqlDataReader r, int ord) where T:struct
{
var t = r.GetSqlValue(ord);
if (t == DBNull.Value) return null;
return ((INullable)t).IsNull ? (T?)null : (T)t;
}
public static T Ref<T>(this SqlDataReader r, int ord) where T : class
{
var t = r.GetSqlValue(ord);
if (t == DBNull.Value) return null;
return ((INullable)t).IsNull ? null : (T)t;
}
Use them like this:
var dd = r.Val<DateTime>(ords[4]);
var ii = r.Def<int>(ords[0]);
int nn = r.Def<int>(ords[0]);
if(reader.IsDBNull(ColumnIndex)) {// logic} works as many answers says.
And I want to mention if you working with column names, just comparing types may be more comfortable.
if(reader["TeacherImage"].GetType() == typeof(DBNull)) { //logic }
I don't think there's a NULL column value, when rows are returned within a datareader using the column name.
If you do datareader["columnName"].ToString(); it will always give you a value that can be a empty string (String.Empty if you need to compare).
I would use the following and wouldn't worry too much:
employee.FirstName = sqlreader["columnNameForFirstName"].ToString();
You can write a Generic function to check Null and include default value when it is NULL. Call this when reading Datareader
public T CheckNull<T>(object obj)
{
return (obj == DBNull.Value ? default(T) : (T)obj);
}
When reading the Datareader use
while (dr.Read())
{
tblBPN_InTrRecon Bpn = new tblBPN_InTrRecon();
Bpn.BPN_Date = CheckNull<DateTime?>(dr["BPN_Date"]);
Bpn.Cust_Backorder_Qty = CheckNull<int?>(dr["Cust_Backorder_Qty"]);
Bpn.Cust_Min = CheckNull<int?>(dr["Cust_Min"]);
}
One way to do it is to check for db nulls:
employee.FirstName = (sqlreader.IsDBNull(indexFirstName)
? ""
: sqlreader.GetString(indexFirstName));
This Solution is less vendor-dependent and works with an SQL, OleDB, and MySQL Reader:
public static string GetStringSafe(this IDataReader reader, int colIndex)
{
return GetStringSafe(reader, colIndex, string.Empty);
}
public static string GetStringSafe(this IDataReader reader, int colIndex, string defaultValue)
{
if (!reader.IsDBNull(colIndex))
return reader.GetString(colIndex);
else
return defaultValue;
}
public static string GetStringSafe(this IDataReader reader, string indexName)
{
return GetStringSafe(reader, reader.GetOrdinal(indexName));
}
public static string GetStringSafe(this IDataReader reader, string indexName, string defaultValue)
{
return GetStringSafe(reader, reader.GetOrdinal(indexName), defaultValue);
}
What I tend to do is replace the null values in the SELECT statement with something appropriate.
SELECT ISNULL(firstname, '') FROM people
Here I replace every null with a blank string. Your code won't throw in error in that case.
By influencing from getpsyched's answer, I created a generic method which checks column value by its name
public static T SafeGet<T>(this System.Data.SqlClient.SqlDataReader reader, string nameOfColumn)
{
var indexOfColumn = reader.GetOrdinal(nameOfColumn);
return reader.IsDBNull(indexOfColumn) ? default(T) : reader.GetFieldValue<T>(indexOfColumn);
}
Usage:
var myVariable = SafeGet<string>(reader, "NameOfColumn")
Check sqlreader.IsDBNull(indexFirstName) before you try to read it.
As an addition to the answer by marc_s, you can use a more generic extension method to get values from the SqlDataReader:
public static T SafeGet<T>(this SqlDataReader reader, int col)
{
return reader.IsDBNull(col) ? default(T) : reader.GetFieldValue<T>(col);
}
how to about creating helper methods
For String
private static string MyStringConverter(object o)
{
if (o == DBNull.Value || o == null)
return "";
return o.ToString();
}
Usage
MyStringConverter(read["indexStringValue"])
For Int
private static int MyIntonverter(object o)
{
if (o == DBNull.Value || o == null)
return 0;
return Convert.ToInt32(o);
}
Usage
MyIntonverter(read["indexIntValue"])
For Date
private static DateTime? MyDateConverter(object o)
{
return (o == DBNull.Value || o == null) ? (DateTime?)null : Convert.ToDateTime(o);
}
Usage
MyDateConverter(read["indexDateValue"])
Note: for DateTime declare varialbe as
DateTime? variable;
None of these was quite what i wanted:
public static T GetFieldValueOrDefault<T>(this SqlDataReader reader, string name)
{
int index = reader.GetOrdinal(name);
T value = reader.IsDBNull(index) ? default(T) : reader.GetFieldValue<T>(index);
return value;
}
I think you would want to use:
SqlReader.IsDBNull(indexFirstName)
We use a series of static methods to pull all of the values out of our data readers. So in this case we'd be calling DBUtils.GetString(sqlreader(indexFirstName)) The benefit of creating static/shared methods is that you don't have to do the same checks over and over and over...
The static method(s) would contain code to check for nulls (see other answers on this page).
You may use the conditional operator:
employee.FirstName = sqlreader["indexFirstName"] != DBNull.Value ? sqlreader[indexFirstName].ToString() : "";
There are a lot of answers here with useful info (and some wrong info) spread about, I'd like to bring it all together.
The short answer to the question is to check for DBNull - almost everyone agrees on this bit :)
Rather than using a helper method to read nullable values per SQL data type a generic method allows us to address this with a lot less code. However, you can't have a single generic method for both nullable value types and reference types, this is discussed at length in
Nullable type as a generic parameter possible? and C# generic type constraint for everything nullable.
So, following on from the answers from #ZXX and #getpsyched we end up with this, 2 methods for getting nullable values and I've added a 3rd for non-null values (it completes the set based on method naming).
public static T? GetNullableValueType<T>(this SqlDataReader sqlDataReader, string columnName) where T : struct
{
int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
return sqlDataReader.IsDBNull(columnOrdinal) ? (T?)null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}
public static T GetNullableReferenceType<T>(this SqlDataReader sqlDataReader, string columnName) where T : class
{
int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
return sqlDataReader.IsDBNull(columnOrdinal) ? null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}
public static T GetNonNullValue<T>(this SqlDataReader sqlDataReader, string columnName)
{
int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
return sqlDataReader.GetFieldValue<T>(columnOrdinal);
}
I usually use column names, alter these if you use column indexes. Based on these method names I can tell whether I'm expecting the data to be nullable or not, quite useful when looking at code written a long time ago.
Tips;
Not having nullable columns in the database avoids this issue. If you
have control over the database then columns should be non-null by
default and only nullable where necessary.
Don't cast database values
with the C# 'as' operator because if the cast is wrong it will
silently return null.
Using a default value expression will change
database nulls to non-null values for value types like int, datetime,
bit etc.
Lastly, whilst testing the above methods across all SQL Server data types I discovered you can't directly get a char[] from a SqlDataReader, if you want a char[] you will have to get a string and use ToCharArray().
I am using the code listed below to handle null cells in an Excel sheet that is read in to a datatable.
if (!reader.IsDBNull(2))
{
row["Oracle"] = (string)reader[2];
}
private static void Render(IList<ListData> list, IDataReader reader)
{
while (reader.Read())
{
listData.DownUrl = (reader.GetSchemaTable().Columns["DownUrl"] != null) ? Convert.ToString(reader["DownUrl"]) : null;
//没有这一列时,让其等于null
list.Add(listData);
}
reader.Close();
}
and / or use ternary operator with assignment:
employee.FirstName = rdr.IsDBNull(indexFirstName))?
String.Empty: rdr.GetString(indexFirstName);
replace the default (when null) value as appropriate for each property type...
This method is dependent on indexFirstName which should be the zero-based column ordinal.
if(!sqlReader.IsDBNull(indexFirstName))
{
employee.FirstName = sqlreader.GetString(indexFirstName);
}
If you don't know the column index but wan't to check a name you can use this extension method instead:
public static class DataRecordExtensions
{
public static bool HasColumn(this IDataRecord dr, string columnName)
{
for (int i=0; i < dr.FieldCount; i++)
{
if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}
}
And use the method like this:
if(sqlReader.HasColumn("FirstName"))
{
employee.FirstName = sqlreader["FirstName"];
}
Old question but maybe someone still need an answer
in real i worked around this issue like that
For int :
public static object GatDataInt(string Query, string Column)
{
SqlConnection DBConn = new SqlConnection(ConnectionString);
if (DBConn.State == ConnectionState.Closed)
DBConn.Open();
SqlCommand CMD = new SqlCommand(Query, DBConn);
SqlDataReader RDR = CMD.ExecuteReader();
if (RDR.Read())
{
var Result = RDR[Column];
RDR.Close();
DBConn.Close();
return Result;
}
return 0;
}
the same for string just return "" instead of 0 as "" is empty string
so you can use it like
int TotalPoints = GatDataInt(QueryToGetTotalPoints, TotalPointColumn) as int?;
and
string Email = GatDatastring(QueryToGetEmail, EmailColumn) as string;
very flexible so you can insert any query to read any column and it'll never return with error
Here is helper class which others can use if they need based on #marc_s answer:
public static class SQLDataReaderExtensions
{
public static int SafeGetInt(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.IsDBNull(fieldIndex) ? 0 : dataReader.GetInt32(fieldIndex);
}
public static int? SafeGetNullableInt(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.GetValue(fieldIndex) as int?;
}
public static string SafeGetString(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.IsDBNull(fieldIndex) ? string.Empty : dataReader.GetString(fieldIndex);
}
public static DateTime? SafeGetNullableDateTime(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.GetValue(fieldIndex) as DateTime?;
}
public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName)
{
return SafeGetBoolean(dataReader, fieldName, false);
}
public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName, bool defaultValue)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.IsDBNull(fieldIndex) ? defaultValue : dataReader.GetBoolean(fieldIndex);
}
}
in c# 7.0 we can do :
var a = reader["ERateCode"] as string;
var b = reader["ERateLift"] as int?;
var c = reader["Id"] as int?;
so it will keep null value if it is.
I did my best to reimplement the Field method from DataTable. https://learn.microsoft.com/en-us/dotnet/api/system.data.datarowextensions.field
It will throw if you try to convert a DBNull.Value to a non-nullable type. Otherwise it will convert DBNull.Value to null.
I haven't fully tested it.
public static T Field<T>(this SqlDataReader sqlDataReader, string columnName)
{
int columnIndex = sqlDataReader.GetOrdinal(columnName);
if (sqlDataReader.IsDBNull(columnIndex))
{
if (default(T) != null)
{
throw new InvalidCastException("Cannot convert DBNULL value to type " + typeof(T).Name);
}
else
{
return default(T);
}
}
else
{
return sqlDataReader.GetFieldValue<T>(columnIndex);
}
}
Usage:
string fname = sqlDataReader.Field<string>("FirstName");
int? age = sqlDataReader.Field<int?>("Age");
int yearsOfExperience = sqlDataReader.Field<int?>("YearsEx") ?? 0;
Convert handles DbNull sensibly.
employee.FirstName = Convert.ToString(sqlreader.GetValue(indexFirstName));
neat one-liner:
while (dataReader.Read())
{
employee.FirstName = (!dataReader.IsDBNull(dataReader.GetOrdinal("FirstName"))) ? dataReader["FirstName"].ToString() : "";
}
you can ever check for this as well
if(null !=x && x.HasRows)
{ ....}
I'm using a SQLdatareader to build POCOs from a database. The code works except when it encounters a null value in the database. For example, if the FirstName column in the database contains a null value, an exception is thrown.
employee.FirstName = sqlreader.GetString(indexFirstName);
What is the best way to handle null values in this situation?
You need to check for IsDBNull:
if(!SqlReader.IsDBNull(indexFirstName))
{
employee.FirstName = sqlreader.GetString(indexFirstName);
}
That's your only reliable way to detect and handle this situation.
I wrapped those things into extension methods and tend to return a default value if the column is indeed null:
public static string SafeGetString(this SqlDataReader reader, int colIndex)
{
if(!reader.IsDBNull(colIndex))
return reader.GetString(colIndex);
return string.Empty;
}
Now you can call it like this:
employee.FirstName = SqlReader.SafeGetString(indexFirstName);
and you'll never have to worry about an exception or a null value again.
You should use the as operator combined with the ?? operator for default values. Value types will need to be read as nullable and given a default.
employee.FirstName = sqlreader[indexFirstName] as string;
employee.Age = sqlreader[indexAge] as int? ?? default(int);
The as operator handles the casting including the check for DBNull.
employee.FirstName = sqlreader[indexFirstName] as string;
For integers, if you cast to a nullable int, you can use GetValueOrDefault()
employee.Age = (sqlreader[indexAge] as int?).GetValueOrDefault();
or the null-coalescing operator (??).
employee.Age = (sqlreader[indexAge] as int?) ?? 0;
IsDbNull(int) is usually much slower than using methods like GetSqlDateTime and then comparing to DBNull.Value. Try these extension methods for SqlDataReader.
public static T Def<T>(this SqlDataReader r, int ord)
{
var t = r.GetSqlValue(ord);
if (t == DBNull.Value) return default(T);
return ((INullable)t).IsNull ? default(T) : (T)t;
}
public static T? Val<T>(this SqlDataReader r, int ord) where T:struct
{
var t = r.GetSqlValue(ord);
if (t == DBNull.Value) return null;
return ((INullable)t).IsNull ? (T?)null : (T)t;
}
public static T Ref<T>(this SqlDataReader r, int ord) where T : class
{
var t = r.GetSqlValue(ord);
if (t == DBNull.Value) return null;
return ((INullable)t).IsNull ? null : (T)t;
}
Use them like this:
var dd = r.Val<DateTime>(ords[4]);
var ii = r.Def<int>(ords[0]);
int nn = r.Def<int>(ords[0]);
if(reader.IsDBNull(ColumnIndex)) {// logic} works as many answers says.
And I want to mention if you working with column names, just comparing types may be more comfortable.
if(reader["TeacherImage"].GetType() == typeof(DBNull)) { //logic }
I don't think there's a NULL column value, when rows are returned within a datareader using the column name.
If you do datareader["columnName"].ToString(); it will always give you a value that can be a empty string (String.Empty if you need to compare).
I would use the following and wouldn't worry too much:
employee.FirstName = sqlreader["columnNameForFirstName"].ToString();
You can write a Generic function to check Null and include default value when it is NULL. Call this when reading Datareader
public T CheckNull<T>(object obj)
{
return (obj == DBNull.Value ? default(T) : (T)obj);
}
When reading the Datareader use
while (dr.Read())
{
tblBPN_InTrRecon Bpn = new tblBPN_InTrRecon();
Bpn.BPN_Date = CheckNull<DateTime?>(dr["BPN_Date"]);
Bpn.Cust_Backorder_Qty = CheckNull<int?>(dr["Cust_Backorder_Qty"]);
Bpn.Cust_Min = CheckNull<int?>(dr["Cust_Min"]);
}
One way to do it is to check for db nulls:
employee.FirstName = (sqlreader.IsDBNull(indexFirstName)
? ""
: sqlreader.GetString(indexFirstName));
This Solution is less vendor-dependent and works with an SQL, OleDB, and MySQL Reader:
public static string GetStringSafe(this IDataReader reader, int colIndex)
{
return GetStringSafe(reader, colIndex, string.Empty);
}
public static string GetStringSafe(this IDataReader reader, int colIndex, string defaultValue)
{
if (!reader.IsDBNull(colIndex))
return reader.GetString(colIndex);
else
return defaultValue;
}
public static string GetStringSafe(this IDataReader reader, string indexName)
{
return GetStringSafe(reader, reader.GetOrdinal(indexName));
}
public static string GetStringSafe(this IDataReader reader, string indexName, string defaultValue)
{
return GetStringSafe(reader, reader.GetOrdinal(indexName), defaultValue);
}
What I tend to do is replace the null values in the SELECT statement with something appropriate.
SELECT ISNULL(firstname, '') FROM people
Here I replace every null with a blank string. Your code won't throw in error in that case.
By influencing from getpsyched's answer, I created a generic method which checks column value by its name
public static T SafeGet<T>(this System.Data.SqlClient.SqlDataReader reader, string nameOfColumn)
{
var indexOfColumn = reader.GetOrdinal(nameOfColumn);
return reader.IsDBNull(indexOfColumn) ? default(T) : reader.GetFieldValue<T>(indexOfColumn);
}
Usage:
var myVariable = SafeGet<string>(reader, "NameOfColumn")
Check sqlreader.IsDBNull(indexFirstName) before you try to read it.
As an addition to the answer by marc_s, you can use a more generic extension method to get values from the SqlDataReader:
public static T SafeGet<T>(this SqlDataReader reader, int col)
{
return reader.IsDBNull(col) ? default(T) : reader.GetFieldValue<T>(col);
}
how to about creating helper methods
For String
private static string MyStringConverter(object o)
{
if (o == DBNull.Value || o == null)
return "";
return o.ToString();
}
Usage
MyStringConverter(read["indexStringValue"])
For Int
private static int MyIntonverter(object o)
{
if (o == DBNull.Value || o == null)
return 0;
return Convert.ToInt32(o);
}
Usage
MyIntonverter(read["indexIntValue"])
For Date
private static DateTime? MyDateConverter(object o)
{
return (o == DBNull.Value || o == null) ? (DateTime?)null : Convert.ToDateTime(o);
}
Usage
MyDateConverter(read["indexDateValue"])
Note: for DateTime declare varialbe as
DateTime? variable;
None of these was quite what i wanted:
public static T GetFieldValueOrDefault<T>(this SqlDataReader reader, string name)
{
int index = reader.GetOrdinal(name);
T value = reader.IsDBNull(index) ? default(T) : reader.GetFieldValue<T>(index);
return value;
}
I think you would want to use:
SqlReader.IsDBNull(indexFirstName)
We use a series of static methods to pull all of the values out of our data readers. So in this case we'd be calling DBUtils.GetString(sqlreader(indexFirstName)) The benefit of creating static/shared methods is that you don't have to do the same checks over and over and over...
The static method(s) would contain code to check for nulls (see other answers on this page).
You may use the conditional operator:
employee.FirstName = sqlreader["indexFirstName"] != DBNull.Value ? sqlreader[indexFirstName].ToString() : "";
There are a lot of answers here with useful info (and some wrong info) spread about, I'd like to bring it all together.
The short answer to the question is to check for DBNull - almost everyone agrees on this bit :)
Rather than using a helper method to read nullable values per SQL data type a generic method allows us to address this with a lot less code. However, you can't have a single generic method for both nullable value types and reference types, this is discussed at length in
Nullable type as a generic parameter possible? and C# generic type constraint for everything nullable.
So, following on from the answers from #ZXX and #getpsyched we end up with this, 2 methods for getting nullable values and I've added a 3rd for non-null values (it completes the set based on method naming).
public static T? GetNullableValueType<T>(this SqlDataReader sqlDataReader, string columnName) where T : struct
{
int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
return sqlDataReader.IsDBNull(columnOrdinal) ? (T?)null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}
public static T GetNullableReferenceType<T>(this SqlDataReader sqlDataReader, string columnName) where T : class
{
int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
return sqlDataReader.IsDBNull(columnOrdinal) ? null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}
public static T GetNonNullValue<T>(this SqlDataReader sqlDataReader, string columnName)
{
int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
return sqlDataReader.GetFieldValue<T>(columnOrdinal);
}
I usually use column names, alter these if you use column indexes. Based on these method names I can tell whether I'm expecting the data to be nullable or not, quite useful when looking at code written a long time ago.
Tips;
Not having nullable columns in the database avoids this issue. If you
have control over the database then columns should be non-null by
default and only nullable where necessary.
Don't cast database values
with the C# 'as' operator because if the cast is wrong it will
silently return null.
Using a default value expression will change
database nulls to non-null values for value types like int, datetime,
bit etc.
Lastly, whilst testing the above methods across all SQL Server data types I discovered you can't directly get a char[] from a SqlDataReader, if you want a char[] you will have to get a string and use ToCharArray().
I am using the code listed below to handle null cells in an Excel sheet that is read in to a datatable.
if (!reader.IsDBNull(2))
{
row["Oracle"] = (string)reader[2];
}
private static void Render(IList<ListData> list, IDataReader reader)
{
while (reader.Read())
{
listData.DownUrl = (reader.GetSchemaTable().Columns["DownUrl"] != null) ? Convert.ToString(reader["DownUrl"]) : null;
//没有这一列时,让其等于null
list.Add(listData);
}
reader.Close();
}
and / or use ternary operator with assignment:
employee.FirstName = rdr.IsDBNull(indexFirstName))?
String.Empty: rdr.GetString(indexFirstName);
replace the default (when null) value as appropriate for each property type...
This method is dependent on indexFirstName which should be the zero-based column ordinal.
if(!sqlReader.IsDBNull(indexFirstName))
{
employee.FirstName = sqlreader.GetString(indexFirstName);
}
If you don't know the column index but wan't to check a name you can use this extension method instead:
public static class DataRecordExtensions
{
public static bool HasColumn(this IDataRecord dr, string columnName)
{
for (int i=0; i < dr.FieldCount; i++)
{
if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}
}
And use the method like this:
if(sqlReader.HasColumn("FirstName"))
{
employee.FirstName = sqlreader["FirstName"];
}
Old question but maybe someone still need an answer
in real i worked around this issue like that
For int :
public static object GatDataInt(string Query, string Column)
{
SqlConnection DBConn = new SqlConnection(ConnectionString);
if (DBConn.State == ConnectionState.Closed)
DBConn.Open();
SqlCommand CMD = new SqlCommand(Query, DBConn);
SqlDataReader RDR = CMD.ExecuteReader();
if (RDR.Read())
{
var Result = RDR[Column];
RDR.Close();
DBConn.Close();
return Result;
}
return 0;
}
the same for string just return "" instead of 0 as "" is empty string
so you can use it like
int TotalPoints = GatDataInt(QueryToGetTotalPoints, TotalPointColumn) as int?;
and
string Email = GatDatastring(QueryToGetEmail, EmailColumn) as string;
very flexible so you can insert any query to read any column and it'll never return with error
Here is helper class which others can use if they need based on #marc_s answer:
public static class SQLDataReaderExtensions
{
public static int SafeGetInt(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.IsDBNull(fieldIndex) ? 0 : dataReader.GetInt32(fieldIndex);
}
public static int? SafeGetNullableInt(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.GetValue(fieldIndex) as int?;
}
public static string SafeGetString(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.IsDBNull(fieldIndex) ? string.Empty : dataReader.GetString(fieldIndex);
}
public static DateTime? SafeGetNullableDateTime(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.GetValue(fieldIndex) as DateTime?;
}
public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName)
{
return SafeGetBoolean(dataReader, fieldName, false);
}
public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName, bool defaultValue)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.IsDBNull(fieldIndex) ? defaultValue : dataReader.GetBoolean(fieldIndex);
}
}
in c# 7.0 we can do :
var a = reader["ERateCode"] as string;
var b = reader["ERateLift"] as int?;
var c = reader["Id"] as int?;
so it will keep null value if it is.
I did my best to reimplement the Field method from DataTable. https://learn.microsoft.com/en-us/dotnet/api/system.data.datarowextensions.field
It will throw if you try to convert a DBNull.Value to a non-nullable type. Otherwise it will convert DBNull.Value to null.
I haven't fully tested it.
public static T Field<T>(this SqlDataReader sqlDataReader, string columnName)
{
int columnIndex = sqlDataReader.GetOrdinal(columnName);
if (sqlDataReader.IsDBNull(columnIndex))
{
if (default(T) != null)
{
throw new InvalidCastException("Cannot convert DBNULL value to type " + typeof(T).Name);
}
else
{
return default(T);
}
}
else
{
return sqlDataReader.GetFieldValue<T>(columnIndex);
}
}
Usage:
string fname = sqlDataReader.Field<string>("FirstName");
int? age = sqlDataReader.Field<int?>("Age");
int yearsOfExperience = sqlDataReader.Field<int?>("YearsEx") ?? 0;
Convert handles DbNull sensibly.
employee.FirstName = Convert.ToString(sqlreader.GetValue(indexFirstName));
neat one-liner:
while (dataReader.Read())
{
employee.FirstName = (!dataReader.IsDBNull(dataReader.GetOrdinal("FirstName"))) ? dataReader["FirstName"].ToString() : "";
}
you can ever check for this as well
if(null !=x && x.HasRows)
{ ....}
This is my code to check if the value can be converted to given type.
public static object TryParseObject<T>(object valueObject)
{
string value = null;
try
{
value = Convert.ToString(valueObject);
var converter = TypeDescriptor.GetConverter(typeof(T));
if (converter != null)
{
//Cast ConvertFromString(string text) : object to (T)
return (T)converter.ConvertFromString(value);
}
return default(T);
}
catch
{
//Can't parse
}
}
This works as it should be. Now I need to convert from date string to DateTime with custom format only (say for ex. format: "MM.DD.YYYY HH:mm:ss"). How can I achieve that?
Should I handle the DateTime scenario separately? like:
if(typeof(T) == typeof(DateTime)) {...}
else {...}
I'm looking to see if I can use the existing code with few changes. Thanks.
If you change a method signature to the same as other TryParse.. methods
public static bool TryParseObject<T>(object valueObject, out T outValue)
{
//your parsing code
}
Then you can create overloads for any type you want. With separated method your code stay clean and easy to understand
public static bool TryParseObject(object valueObject, out DateTime outValue)
{
const string EXACT_FORMAT = "MM.DD.YYYY HH:mm:ss";
// your parsing to DateTime
}
public static bool TryParseObject(object valueObject, out int outValue)
{
// your parsing to int
}
Essentially, I'm trying to use
field.SetValue(obj, val);
Where val's type can be implicitly converted to the true field type, but isn't 'directly' assignable. Of course, I get the usual ArgumentException: Object type cannot be converted to target type. Is there a way to do this without manually finding and calling the constructor?
Try with Convert.ChangeType:
field.SetValue(obj, Convert.ChangeType(val, field.PropertyType), null);
Check the following Setting a property by reflection with a string value for further info.
This is a quite complicated question, and I don't know a way to make it work in a sigle line. While the Convert.ChangeType works for simple cases, it will fail in the following cases :
The target type is a Nullable enum, and you use an integer value (not an enum value) : in that case, you'l need use Enum.ToObject to make it work.
Your value is DBNull.Value : You will need to test it and assign null in that case
The target type is not the same type of number as the value you want to set : Convert.ChangeType will help you here.
Here's a sample that illustrates how to do it :
public void SetFieldValue(FieldInfo field, object targetObj, object value)
{
object valueToSet;
if (value == null || value == DBNull.Value)
{
valueToSet = null;
}
else
{
Type fieldType = field.FieldType;
//assign enum
if (fieldType.IsEnum)
valueToSet = Enum.ToObject(fieldType, value);
//support for nullable enum types
else if (fieldType.IsValueType && IsNullableType(fieldType))
{
Type underlyingType = Nullable.GetUnderlyingType(fieldType);
valueToSet = underlyingType.IsEnum ? Enum.ToObject(underlyingType, value) : value;
}
else
{
//we always need ChangeType, it will convert the value to the proper number type, for example.
valueToSet = Convert.ChangeType(value, fieldType);
}
}
field.SetValue(targetObj, valueToSet);
}
A unit test for the function :
enum TestEnum
{
DummyValue
}
class TestClass
{
public int IntValue;
public decimal DecimalValue;
public int? NullableInt;
public TestEnum EnumValue;
public TestEnum? NullableEnumValue;
public TestClass ObjectValue;
}
[TestFixture]
public class DataObjectBinderFixture
{
private TestClass _testObject;
private void SetFieldValue(string fieldName, object value)
{
var fieldInfo = typeof (TestClass).GetField(fieldName);
ReflectionUtils.SetFieldValue(fieldInfo, _testObject, value);
}
[Test]
public void TestSetValue()
{
_testObject = new TestClass();
SetFieldValue("IntValue", 2.19);
SetFieldValue("IntValue", DBNull.Value);
SetFieldValue("DecimalValue", 1);
SetFieldValue("NullableInt", null);
SetFieldValue("NullableInt", 12);
SetFieldValue("EnumValue", TestEnum.DummyValue);
SetFieldValue("EnumValue", 0);
SetFieldValue("NullableEnumValue", TestEnum.DummyValue);
SetFieldValue("NullableEnumValue", null);
SetFieldValue("NullableEnumValue", 0);
SetFieldValue("NullableEnumValue", DBNull.Value);
SetFieldValue("ObjectValue", DBNull.Value);
}
}
I am attempting to cast a string gotten from a local database into decimal, but resulted in a "Cannot implicitly convert Type 'GlobalCurrencyConverter.CurrencyRateDataSet.rateDataTable' to decimal".
Below is my code,
protected decimal calRate_Click(object sender, EventArgs e)
{
CurrencyRateDataSetTableAdapters.rateTableAdapter rateTable;
decimal exRate = (decimal)rateTable.GetDataBySourceTargetCurrency(objDropDownSourceCUR.SelectedValue, objDropDownTargetCUR.SelectedValue);
decimal exAmt = 0;
exAmt = (decimal)Convert.ToDecimal(objTextBoxSourceAmt.Text);
}
Update:
rateTable.getDataBySourceTargetCurrency is a method created in Visual Studio Designer. It takes in 2 parameters and search through the local database, returning a single row (and single column) of value.
If "rateTable.GetDataBySourceTargetCurrency(objDropDownSourceCUR.SelectedValue, objDropDownTargetCUR.SelectedValue)" is in fact a string (from your title), try Decimal.TryParse():
//Just noticed questions above... if rateTable.GetDataBy... does not return a string, you must create a string from whatever object it does return, then use TryParse()
Decimal exRate;
String exRateString = rateTable.GetDataBySourceTargetCurrency(objDropDownSourceCUR.SelectedValue, objDropDownTargetCUR.SelectedValue);
bool convertSuccessful = Decimal.TryParse(exRateString, out exRate);
if (convertSuccessful == true)
{
// do some stuff here
}
else
{
// report error
}
Try this:
public static class StringUtils
{
public static string ToCurrency(decimal value)
{
return value.ToString("C");
}
public static decimal FromCurrency(string value)
{
return decimal.Parse(value, NumberStyles.Currency);
}
public static decimal? FromCurrency(string value, decimal? defaultValue)
{
decimal num;
if(decimal.TryParse(value, NumberStyles.Currency, null, out num))
return num;
return defaultValue;
}
}
From which you can do this:
decimal exAmt = StringUtils.FromCurrency(objTextBoxSourceAmt.Text,0);