I am trying to extend the Field method of the DataRow extension to add a parameter to check if the column exists:
public static T? FieldValue<T>(
this DataRow row,
string columnName,
bool checkColumn = false) where T : struct
{
return
checkColumn
&& !row.Table.Columns.Contains(
columnName)
? default(T)
: row.Field<T>(
columnName);
}
This works fine for int, datetime etc. However, when I try to use it with string it shows error:
The type string must be non-nullable
I am also getting error if there is a null value in the database:
Cannot cast DBNull.Value to type 'System.Decimal'
Is there a way it can be extended the Dataextension seamlessly?
As Markus pointed out, you've got two challenges here. One is about structs vs value types and the other one is to do with the fact that you have to deal with null values.
The second one is trivial to tackle: Just add a ? to your implementation like this: row.Field<T?>(columnName) and your exceptions will be gone.
The first problem, however, is a nasty and frequently encountered one. I am unaware of a pretty way of solving this. Let me still suggest something:
Based on your code above I assume that you are happy to get back Nullable types even for non-nullable columns. So here is something you could do to support reference types on top of what you have and still avoid too much code duplication:
// value type version
public static T? FieldValueStruct<T>(this DataRow row, string columnName, bool checkColumn = false)
where T : struct
{
return row.GetValue(columnName, checkColumn, default(T), row.Field<T? /*with a question mark!!!*/ >);
}
// reference type version
public static T FieldValueClass<T>(this DataRow row, string columnName, bool checkColumn = false)
where T : class
{
return row.GetValue(columnName, checkColumn, default(T), row.Field<T>);
}
// shared amongst value and reference type implementation
private static T GetValue<T>(this DataRow row, string columnName, bool checkColumn, T defaultValue, Func<string, T> getter)
{
return checkColumn && !row.Table.Columns.Contains(columnName)
? defaultValue
: getter(columnName);
}
With this code in place, you get the functionality you want but at a price: You will need to specify type parameters (just like you do now) when you call these methods because type inference won't work (here is why).
string s;
// no type inference, type parameter must be specified
s = row.FieldValueClass<string>("test");
Also, you will need to differentiate in your calls between the value type version and the reference type version which simply isn't pretty. Why do we need to use two different names for the methods? The reason for that is that you cannot overload methods by simply adding different type constraints (see here).
The type inference topic could be solved by using an out parameter which, however, comes again with a bunch of downsides...
// value type version that works with type inference
public static void FieldValueStruct<T>(this DataRow row, string columnName, out T? value, bool checkColumn = false)
where T : struct
{
value = row.GetValue(columnName, checkColumn, default(T), row.Field<T?>);
}
// reference type version that works with type inference
public static void FieldValueClass<T>(this DataRow row, string columnName, out T value, bool checkColumn = false)
where T : class
{
value = row.GetValue(columnName, checkColumn, default(T), row.Field<T>);
}
Now, you can call your method without the type parameter like this:
string s;
// with type inference, doesn't work with properties, though, only with fields
row.FieldValueClass("test", out s);
Unfortunately, this does not work with properties - only with fields.
You see, the world is evil and, sometimes, we cannot do too much about it. ;)
Update based on your comment:
The code below changes your semantics a little but perhaps that's ok:
public static T FieldValue<T>(this DataRow row, string columnName, bool checkColumn = false)
{
return checkColumn && !row.Table.Columns.Contains(columnName)
? default(T)
: row.Field<T>(columnName);
}
Calling this method would need to look like:
// this will return 0 if the column is not available, a null value from the database will cause an exception
int i = r.FieldValue<int>("test");
// this will return null if the column is not available, a null value from the database would be ok
int? iNullable = r.FieldValue<int?>("test");
// this will return null if the column is not available, a null value from the database would be ok
string s = r.FieldValue<string>("test");
The reason for the first error message is the where-constraint:
where T : struct
This constraint requires that each type T that is used as a type parameter is a value type. string is a reference type, hence the error message. In order tomsolve the problem, you should remove the constraint if you don't need it.
As regards the Null-value problem, you should check whether the column is null (if it exists) and in this case also return default(T). You can use the DataRow.IsNull method to check whether the cell is null.
Related
Tell me please is this is correct way to check NULL in DataRow if need to return a string
Convert.ToString(row["Int64_id"] ?? "")
Or should be like check with DBNull.Value.
Need to so much more smaller than
if(row["Int64_id"] != DBNull.Value){...}else if{}
Check if the data column is not null with DataRow.IsNull(string columnName)
if (!row.IsNull("Int64_id"))
{
// here you can use it safety
long someValue = (long)row["Int64_id"];
}
There are overloads for it using the index of the column or if you have the instance of the DataColumn. If you are sure about the index, use the index version which tends to be faster than the other options.
We have created an extension class that helps in these kinds of situations.
public static class DataRowExtensions
{
public static T FieldOrDefault<T>(this DataRow row, string columnName)
{
return row.IsNull(columnName) ? default(T) : row.Field<T>(columnName);
}
}
You can use is as follows:
int id = dataRow.FieldOrDefault<int>("Id");
I am trying to generalize some code using a C# generics. I ran into a problem on the line:
T defaultValue = (intList.Count == 1) ? intList[0] : GetDefaultValue(typeParameter);
with the error:
Error CS0173 Type of conditional expression cannot be determined because there is no implicit conversion between 'T [PathToFile(2035)]' and 'T [PathToFile(2058)]'
Below is the entire code:
private void ParseMultIndiciedList<T>(int count, params List<T>[] lists)
{
foreach (List<T> intList in lists)
{
//intList.Count should never be bigger than count
if (intList.Count > count)
{
throw new Exception("Multi-variable argument indicies do not match");
}
//If only 1 variable is assigned to the list, it is considered the default
Type typeParameter = typeof(T);
T defaultValue = (intList.Count == 1) ? intList[0] : GetDefaultValue(typeParameter);
int intListIntialCount = intList.Count;
if (intList.Count < count)
{
for (int countOffset = 0; countOffset < count-intListIntialCount; countOffset++)
{
intList.Add(defaultValue);
}
}
}
}
private T GetDefaultValue<T>(Type inputType)
{
switch (Type.GetTypeCode(typeof(T)))
{
case TypeCode.Int32:
return (T) Convert.ChangeType(-1, typeof (T));
}
return default(T);
}
I'm having trouble understand what the problem in the compiler is pertaining to the Type object, if I've directed a Type out of the first generic call in ParseMultiIndiciedList shouldn't I be Ok in the second generic method call to GetDefaultValue?
This significantly reduced code reproduces the same issue:
private void Foo<T>(List<T> intList)
{
Type typeParameter = typeof(T);
T defaultValue = (intList.Count == 1) ? intList[0] : GetDefaultValue(typeParameter);
}
private T GetDefaultValue<T>(Type inputType)
{
return default(T);
}
And yields not one, but two compiler errors (just as the original code did). The relevant one, apart from the one you mention in your question:
CS0411 The type arguments for method GetDefaultValue<T>(Type) cannot be inferred from the usage. Try specifying the type arguments explicitly.
This is because the compiler can't infer what T you want to pass to GetDefaultValue<T>() (because it's not used as a parameter type). Even though both are called T, they have no relation whatsoever, since they're specified on separate methods.
So in order to fix this, you'll have to pass the type argument for T explicitly:
GetDefaultValue<T>(typeParameter);
Now you tell that GetDefaultValue()'s generic argument T should be fed from Foo()'s T.
The error would be clearer if you'd rename the Ts:
private void Foo<TListItem>(List<TListItem> intList)
private TValue GetDefaultValue<TValue>(Type inputType)
Now the error would read:
Type of conditional expression cannot be determined because there is no implicit conversion between 'TListItem' and 'TValue '
And it'd be clear that they're two totally unrelated generic parameters.
But I'd consider a redesign altogether. The error in your question mentions that you're at line 2000 (!) of this code file, which is a maintenance disaster waiting to happen.
Put these two methods in their own class, and give it one type parameter. Then GetDefaultValue() can lose the T and use that of its containing class:
public class MultIndicedListParser<T>
{
public void Parse(int count, params List<T>[] lists)
{
// ...
}
private T GetDefaultValue(Type inputType)
{
// ...
}
}
The type parameter T in ParseMultIndiciedList<T> declaration is independent of type parameter T in GetDefaultValue<T> declaration. Therefore you need to explicitly pass type when calling GetDefaultValue from ParseMultIndiciedList:
T defaultValue = (intList.Count == 1) ? intList[0] : GetDefaultValue<T>(typeParameter);
^^^
The error you are getting shows that it's confused about what the return type of function might be in else part. If you add type information while calling that function it should be fine.
T defaultValue = (intList.Count == 1) ? intList[0] : GetDefaultValue<T>(typeParameter);
The above change should resolve the issue.
I have a table with a few fields, one of them is a Double type field which can contains null values...
Using ADO and SQLDATAReader I recover that field in a variable. I defined this variable as a: Double, Double?, double, double?... and I got the value (coming from de SQLDataReader) using GetValue (and doing a cast) or using a GetDouble... each one is crashing when the value is null.
The only this is working is defining this variable as a object, but I dont want it. Thinking in advance could be hard times handle this type in my project...
Quote: I have to differentiate the case when this value is 0 or null...
Any idea guys?
Edited:
Object.DoubleValue= (Double?)Datos.GetDouble(1);
Object.doubleValue= (double?)Datos.GetDouble(1);
Not working.
Object.ObjectValue= Datos.GetValue(1);
Working.
Unfortunately there's no out of the box method. But you could tweak it with an extension method like this:
(be aware its just a rough prototype that works in your case, but probably needs some checks and constraints etc)
public static class Helpers
{
public static T GetSmartValue<T>(this SqlDataReader r, int ordinal)
{
dynamic value = r.GetValue(ordinal);
if (value == DBNull.Value)
{
value = null;
return value;
}
return (T) value;
}
}
then in your code
var x = yourReader.GetSmartValue<double?>(1);
I have a lambda expression which accepts, a int? (nullable integer),
which returns value if value exists or DBNull.Value otherwise.
Func<int?, object> getId = id => id.HasValue ? id.Value : (object)DBNull.Value;
The goal here is that, I want to make that expression slightly a bit more generic so that I can pass any nullable types like, DateTime?
So here is a non-functional code I was starting off with, but not sure where to specify nullable's type.
int? imageId;
DateTime? actionDate;
Func<Nullable<T>, object> getValue =
id => id.HasValue ? id.Value : (object) DBNull.Value;
SaveImage(getValue(imageId), getValue(actionDate));
Is it possible to specify generic type or should I create a named function to do so?
Since the purpose of the question is to use a lambda expression, here is a solution. It takes a different route by using weak typing instead of the proposed strong typing, but accomplishes the same thing nonetheless.
// A lambda solution
Func<object, object> fnGetValue =
v =>
ReferenceEquals(v, null)
? DBNull.Value
: v;
// Sample usage
int? one = 1;
int? two = null;
object o1 = fnGetValue(one); // gets 1
object o2 = fnGetValue(two); // gets DBNull
Edit: This loose typing works because the data type of the lambda argument v is of the struct itself and is not the Nullable type wrapper. Apparently the Nullable value that the caller uses has been resolved or 'unwrapped' by the time it hits the lambda argument and the lambda argument reveals a struct value or null; the Nullable wrapper is nowhere to be seen at this point (or as far as I can find). This behaviour can be proved by putting a debug breakpoint in the lambda at v and inspecting its value.
The good side effect of this behaviour is the lambda works equally well for both Nullable and non-Nullable types -- it's not restricted.
Instead of using generics, you can just make an extension method on Object to do the conversion.
Here's a sample program. The ToDbObject extension does the conversion:
using System;
static class Program
{
static object ToDbObject(this object value)
{
return value ?? DBNull.Value;
}
static void Main(string[] args)
{
int? imageId = 3;
DateTime? actionDate = null;
Console.WriteLine("ImageId {0}: [{1}] - {2}", imageId, imageId.ToDbObject(), imageId.ToDbObject().GetType());
Console.WriteLine("actionDate {0}: [{1}] - {2}", actionDate, actionDate.ToDbObject(), actionDate.ToDbObject().GetType());
Console.ReadKey();
}
}
The above prints:
ImageId 3: [3] - System.Int32
actionDate : [] - System.DBNull
It's correctly handling both cases.
I think you can do it by creating a delegate factory method where you can specify the generic type parameter:
public static Func<Nullable<T>, object> CreateGetValueFunc<T>() where T : struct
{
return id => id.HasValue ? id.Value : (object)DBNull.Value;
}
And you can use it in your example like this:
SaveImage(
CreateGetValueFunc<int>()(imageId),
CreateGetValueFunc<DateTime>()(actionDate));
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;