Pros and Cons of using "as", "is", "DBNull", ToString() in C# - c#

When reading data from ExecuteReader. It returns me data in IDataReader. When I want to fill this data into my entities I will be assigning Non Nullable values in most of the cases to either Nullable types or Non Nullable types, e.g.:
int id;
string name;
int? age;
byte[] image;
example e = new example();
e.id = (int)dr["id"]; //unbox
e.name = dr["name"].ToString() //will result in empty string if dr["name"] is DBNull
e.name = dr["name"] is DBNull ? null : dr["name"].ToString();
e.age = dr["age"] as int?; // way 1
e.age = dr["age"] is DBNull ? null : (int?)dr["age"]; //way 2
e.image = dr["image"] as byte?;
EDIT
if id is primary key for the table and is NonNullable. But it is being used in another table where this key could be NULL then what should be the approach. Should that entity become NULL or exception should be thrown.

If the object reference is null, the is operator always returns false because there is no object available to check its type.
if (o is Employee) {
Employee e = (Employee) o;
// Use e within the ‘if’ statement.
}
The as operator works just like casting except the as operator will never throw an exception. Instead, if the object can’t be cast, the result is null.
Employee e = o as Employee;
if (e != null) {
// Use e within the ‘if’ statement.
}
Check more : C# is and as operators

If you're sure in result type and it can't be null:
(int)dr["id"]
If result can be null and you know the type:
dr["age"] as int?;
dr["age"] as int? ?? -1; // default value
If result can't be null and you don't know the type:
Convert.ToInt32(dr["age"]);
If result can be null and you don't know the type:
object age = dr["age"]; // can be short, int, etc
!Convert.IsDBNull(age) ? Convert.ToInt32(age) : -1;

My assignments would look as such:
e.id = (int)dr["id"]; //unbox
e.name = dr["name"] as string; // String if string, null if DbNull
e.age = dr["age"] as int?;
e.image = dr["image"] as byte?;
The as operator works best for reference types (such as strings or nullables), because it will return null should the object I'm casting be something else, like a DbNull. This is exactly the same as doing the check manually, but more terse and easy to understand.

Look at the answers to this question for some good examples of generic helper functions for your problem.

In (comments) you asked for more info on my dapper comment; the point I was trying to make is that this is essentially a solved problem, and there are a range of tools to help here, from the complex and feature-rich ORMs (NHibernate, Entity Framework, LLBLGenPro), through middle-grounds (LINQ-to-SQL etc), through to stupidly simple (but very effective) micro-ORMs (dapper-dot-net, Peta.Poco, Simple.Data).
dapper-dot-net is the (freely available, OSS) micro-ORM that underpins stackoverflow/stackexchange. It is based on the ridiculously simple approach of mapping directly from returned column names to member names (fields or properties), such that the usage is simply:
var connection = ... // an open connection
int id = ... // a record to fetch
var singleItem = connection.Query<example>(
"select * from example where id = #id", new {id}).Single();
or
int customerId = ...
var orders = connection.Query<Order>(
"select * from Orders where Status = 'Open' and CustomerId = #customerId",
new {customerId}).ToList();
where example is the type in your code, with members name, id, etc. It takes care of all the heavy lifting:
mapping inputs to parameters (see #id and #customerId, and how their values are provided)
materializing returned records into objects in a very optimised way
a few other tricks like horizontal multi-mapping, multi-grid mapping, automatic IN handling, etc
this means that you don't have to worry about details like null, DBNull, Nullable<T> etc, since dapper already handles all that for you, and is (thanks to some IL voodoo) just as fast at runtime as writing the materialization code yourself.

Related

Handling null with dataset and LINQ

I have a method which uses LINQ to look up a company in a local DataSet, using a company id (Guid). The properties from the data set is then stored in a class (CompanyModel), which is created for the occasion. One of the properties is the OrganizationNo, which is of type Long in the DataSet, but String in the CompanyModel.
The issue is, OrganizationNo can be null in the DataSet. This causes the ToString() method to throw an exception. I can tried fixing it (see below), but with no luck.
Isn't there some sort of simple, easy to read solution for this, which does not require multiple lines of code and/or use of try/catch?
UPDATE
The exception is thrown at an earlier point, since query becomes null if the organization number is null. Hence it has nothing to do with ToString() failing. The code never gets that far. I have tried changing the NullValue property of the OrganizationNo in the data table from (Throw Exception) to (Null), but this is not allowed. Is it a bad design we have chosen, using Long for a property which is sometimes null?
public void SelectCompany(Guid companyId)
{
SelectedCompany = new CompanyModel();
SelectedCompany.Id = companyId;
CompanyDataTabel dt = DataSet.Company;
// Look up the company in the company table in the data Set
var query = from company in dt.AsEnumerable()
where company.Id == companyId
select new
{
company.OrganizationNo,
company.Name,
company.PhoneNo,
company.Email,
};
// If the OrganizationNo is null, then the entire query is null.
// Hence the '.Count()' method will fail.
if (query.Count() != 1)
{
// One (and only one) company should match the organisation ID.
throw new ArgumentException("Multiple companies found.");
}
// These properties are never null and causes no issues
SelectedCompany.Name = query.First().Name;
SelectedCompany.PhoneNumber = query.First().PhoneNo;
SelectedCompany.EmailAddress = query.First().Email;
SelectedCompany.OrganisationNumber = query.First().OrganizationNo.ToString()
}
The problem in your query is that you are using a strongly typed DataSet which throws an error if you try to access a property that is null. To handle that case the DataSet automatically adds a bool method that you have to check before you access that nullable-column property. But your query creates an anonymous type that accesses this nullable property without this check. For that reason you will get the exception when the query is executed which is at query.First().
Use this query instead:
var query = from company in dt.AsEnumerable()
where company.Id == companyId
select new
{
OrganizationNo = company.IsOrganizationNoNull()
? string.Empty
: company.OrganizationNo.ToString(),
company.Name,
company.PhoneNo,
company.Email,
};
Side-note:
Instead of if (query.Count() != 1) you should use Single:
var company = query.Single(); // throws a desired exception if there are no or more than two records
SelectedCompany.Name = company.Name;
SelectedCompany.PhoneNumber = company.PhoneNo;
SelectedCompany.EmailAddress = company.Email;
SelectedCompany.OrganisationNumber = company.OrganizationNo;
This handles the case that there is none or more than one and will throw this exception on both cases. It's more efficient because you only need to execute the query once. Btw, all your query.First() calls must execute the whole query again
you can check OrganisationNumber property is must be define Nulable.. Nulable<long> or long? like this public long? OrganisationNumber {get;set;}
SelectedCompany.OrganisationNumber = string.Empty;
if(query.First() != null && query.First().OrganizationNo.HasValue)
{
SelectedCompany.OrganisationNumber =
query.First().OrganizationNo.Value.ToString();
}
or
SelectedCompany.OrganisationNumber = query.First() != null && query.First().OrganizationNo.HasValue ? "" : "";
or
SelectedCompany.OrganisationNumber =
query.First() != null ? query.First().OrganizationNo?.ToString() ?? string.Empty : string.Empty;
long type variable itself can not be null. but Nullable<long> can
The ?. operator works with null, which works for reference types or nullable value types, but not for normal value types like long.

Best way to handle Datarow DBNull [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Best way to check if a Data Table has a null value in it
I want to know what should be the way to check DBNull for a DataTable - DataRow values.
Ex
I have a DataRow which fetches information from database from rows type like :
varchar, money, Int and so on.
What should be my (simple and sweet) approach to handle such situation.
Try:
foreach(DataRow row in table.Rows)
{
object value = row["ColumnName"];
if (value == DBNull.Value)
{
}
else
{
}
}
Try this
For varchar
string val = dr["name"].ToString();
For int
int? val = dr["status"] == DBNull.Value ? (int?) null : Convert.ToInt32(dr["status"]);
Do the same for Money, Decimal as done for int replacing with respective .Net types
You can use an extension method like this;
public static T GetValue<T>(this OracleDataReader reader, string fieldName)
{
T result = default(T);
int index = reader.GetOrdinal(fieldName);
if (reader.IsDBNull(index))
{
return default(T);
}
if (typeof(T) == typeof(string))
{
result = (T)Convert.ChangeType(reader.GetString(index), typeof(T));
}
if (typeof(T) == typeof(int))
{
result = (T)Convert.ChangeType(reader.GetInt32(index), typeof(T));
}
if (typeof(T) == typeof(DateTime))
{
result = (T)Convert.ChangeType(reader.GetDateTime(index), typeof(T));
}
if (typeof(T) == typeof(byte[]))
{
OracleLob blob = reader.GetOracleLob(index);
result = (T)Convert.ChangeType(blob.Value, typeof(T));
}
return result;
}
And you can use like string title = reader.GetValue<string>("title")
There are clearly-defined mappings for CLR and SQL types, so the question is really how to efficiently and accurately map those types. Long-term, the easiest way is probably to use an automated mapping process which maps the properties of your class to the columns in the DataRow. You can write your own or find many examples/products online (any ORM features this as core functionality).
Assuming that you still want to make manual assignments, you need to determine how you want to handle null values from the database. Do you want to assign them to a corresponding nullable type? do you want to use default(T)? do you want to use another value (default can be a poor substitute for null)? For example, a temperature of 0 degrees is perfectly valid, but default(float) == 0. If you use default(T), you might not be able to tell the difference between zero and a value that was null in the database.
Once you have your assignment strategy defined, put the code into a reusable form (extension methods, helper class, etc.) Prefer unboxing to the exact type when possible, as this will be the fastest. Following that, unbox to type, then cast. Following that, use the Convert class.

Why was I let assign a null value to a bool? What's the best way to resolve this?

I have a Linq to SQL query very similar to the following:
var result = (from shareclass in database.ShareClassInfo
where shareclass.Id == ID
select new ShareClass
{
IsOnlineListing = shareclass.IsOnlineListing
}
);
var list = result.ToList();
When I try to create a list from the results I get the following error:
The null value cannot be assigned to a member with type System.Boolean which is a non-nullable value type.
The reason for this is that IsOnlineListing is a bool, but the field in the database is null. So I'm effectively trying to assign a value type with a null value (which is impossible).
I think the solution to this is to make IsOnlineListing a nullable type, but I'm a little confused why I was let do this in the first place. I mean, the database field is defined as a [bit] NULL field. I thought the compiler would be smarter than to let me assign null value to a non-nullable type, or at least it would warn me about it.
So what I'm wondering, is if this is the correct solution? Is there another way to do this? Why wasn't the compiler able to tell me that this is or could be a problem?
you need to change your declaration of IsOnlineListing
From
bool IsOnlineListing;
To
bool? IsOnlineListing;
Or do something like this
var result = (from shareclass in database.ShareClassInfo
where shareclass.Id == ID
select new ShareClass
{
IsOnlineListing = shareclass.IsOnlineListing.HasValue ?
shareclass.IsOnlineListing.Value : false;
}
);
var list = result.ToList();
You can use a nullable type.

casting problem from SqlDataReader

Suppose i have this sql statement and I have executed a sql command to get a datareader:
"select 1 union select 2"
//.....
var rdr = cmd.ExecuteReader();
and now i want to read the value in the first column of the first row:
var myInt = (int)rdr.GetValue(0); //great this works
var myLong = (long)rdr.GetValue(0); //throws cast exception, even though you can cast int to long
So it appears the type you cast to in C# needs to match exactly the SQL type. I.E. If the sql type is bigint, you can only cast to long. If the sql type is int, you can only cast to int. No mix and match...
I just want to get something that works regardless of the type of integer c# asks for and sql returns, as long as you could theoretically cast one to the other. So if SQL Server gives me a floating point type, and I'm asking for an int, I want the truncated int you get from doing that cast.
My goal is to make this work with generics, so I can have this function work when the generic parameter doesn't exactly match the datatype in sql server:
List<T> GetFirstColumn<T>(string sql) where T : struct
{
//get connection, execute reader
// loop:
// lst.Add( (T) rdr.GetValue(0));
}
I'd like this to work for both statments:
var sql1 = "Select 1"; //sql int
var sql2 = "Select cast(1 as bigint)"; //sql equivalent of a long
var lst1 = GetFirstColumn<int>(sql1);
var lst2 = GetFirstColumn<int>(sql2);
Does anyone have a relatively painless way of doing this?
Like Fredrik says, the value from SqlDataReader is boxed. You can convert a boxed value to an int with Convert.ToInt32, like:
int i = Convert.ToInt32(read[0]);
This will try to convert even if SQL Server returns a bigint or a decimal.
System.Convert will take care of the conversion.
T GetValue<T>(SqlDataReader rdr)
{
var dbVal = rdr.GetValue(0);
var csVal = (T)System.Convert.ChangeType(dbVal, typeof(T));
}
Caveat: if T == Nullable<S>, you need to do some extra work with reflection to get the underlying type and call ChangeType with typeof(S) as the type parameter. Apparently, MS didn't update the ChangeType function with .NET 2.0 and the introduction of nullables. And if it's a nullable, and dbVal is DBNull, you can just return null.
object dbValue = 5;
//this throws
Convert.ChangeType(dbValue, typeof(int?));
//this works
if(dbValue == DBNull.Value || dbValue == null)
{
if(typeof(int?).IsNullable) //or is a class, like string
{return null;}
dbValue = null;
}
var type = GetUnderlyingType<int?>(); //== typeof(int)
Convert.ChangeType(dbValue, type);
I think your problem is that GetValue returns an object. This means that in the case of an int, you will get an int boxed in an object. Then you cannot directly cast it to a long but must first unpack it as an int:
var myLong = (long)(int)rdr.GetValue(0);
This will be quite tricky using generics, I would say. Well, you could make generic methods with two type arguments; one specifying what type the field is, and one specifying the type you want. But I don't really see the need; SqlDataReader already has methods for the various data types, such as GetInt32, GetInt64 and so on, so the generic method would not really give any added value in that case.

What's the recommended way to start using types from a returned DataRow in C#?

When looping through a DataRow and encountering types such as
DataRow dr;
dr["someString"]
dr["someInteger"]
dr["somedata"]
What's the best way to get them into their corresponding data types? dr["foo"] is just a generic object.
Also, are these able to be easily converted to nullable types? dr["someInteger"] could be null.
When reading from a DataRow, your biggest enemy is a null value. In a DataRow, when a value is null, it is not equals to null: It is equals to DBNull.Value.
if(DBNull.Value == null)
{
// Will never happen
}
Unless you know that your field cannot be null, it is not safe to cast. For example, the following example will fail if the data is DBNull:
string name = (string)dr["Name"];
If you can use the LINQ extensions, you can include the reference System.Data.DataSetExtensions and the namespace System.Data and call
string name = dr.Field<string>("Name");
If you cannot use LINQ, then you have to fall back to checking for null value with
string name = null;
if(!dr.IsNull("Name"))
name = (string)dr["Name"];
Or you could code your own Field function like this:
public static T GetValue<T>(object value)
{
if (value == null || value == DBNull.Value)
return default(T);
else
return (T)value;
}
and get your value this way:
string name = GetValue<string>(dr["Name"]);
If you can use .net 3.5, then you can use the Field extension method to more easily access the data if you know the type. An example would be:
string somestring= row.Field<string>("SomeString");
Otherwise you're stuck with casting the field to the type of the object the old fashioned way.
Simply casting the values to the right type should work:
(string) dr["someString"];
(int?) dr["someInteger"];
(byte[]) dr["somedata"];
string GetString(DataRow dr, string ColumnName)
{
if (dr.IsNull(ColumnName))
{
return null;
}
return (string)dr[ColumnName];
}
Another option is to use "as"
string str = dr["someString"] as string;
if it's DBNull.Value (or any other object not of type string), then str will get a real "null". Otherwise it will get the proper string value.
For value types, you can use nullable, i.e.
int? i = dr["someint"] as int?;
Again, it will get a real "null" instead of DBNull.Value. However, with nullable types you have to remember to use .Value, i.e.
int x = i.Value + 5;

Categories