How can I check for db null values in the attached code? Please understand I am a new C# convert...
What this code does is takes a IDataReader object and converts and maps it to a strongly-typed list of objects. But what I am finding is it completely errors out when there are null columns returned in the reader.
Converter
internal class Converter<T> where T : new()
{
// Declare our _converter delegate
readonly Func<IDataReader, T> _converter;
// Declare our internal dataReader
readonly IDataReader dataReader;
// Build our mapping based on the properties in the class/type we've passed in to the class
private Func<IDataReader, T> GetMapFunc()
{
// declare our field count
int _fc = dataReader.FieldCount;
// declare our expression list
List<Expression> exps = new List<Expression>();
// build our parameters for the expression tree
ParameterExpression paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR");
ParameterExpression targetExp = Expression.Variable(typeof(T));
// Add our expression tree assignment to the exp list
exps.Add(Expression.Assign(targetExp, Expression.New(targetExp.Type)));
//does int based lookup
PropertyInfo indexerInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(int) });
// grab a collection of column names from our data reader
var columnNames = Enumerable.Range(0, _fc).Select(i => new { i, name = dataReader.GetName(i) }).AsParallel();
// loop through all our columns and map them properly
foreach (var column in columnNames)
{
// grab our column property
PropertyInfo property = targetExp.Type.GetProperty(column.name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
// check if it's null or not
if (property != null)
{
// build our expression tree to map the column to the T
ConstantExpression columnNameExp = Expression.Constant(column.i);
IndexExpression propertyExp = Expression.MakeIndex(paramExp, indexerInfo, new[] { columnNameExp });
UnaryExpression convertExp = Expression.Convert(propertyExp, property.PropertyType);
BinaryExpression bindExp = Expression.Assign(Expression.Property(targetExp, property), convertExp);
// add it to our expression list
exps.Add(bindExp);
}
}
// add the originating map to our expression list
exps.Add(targetExp);
// return a compiled cached map
return Expression.Lambda<Func<IDataReader, T>>(Expression.Block(new[] { targetExp }, exps), paramExp).Compile();
}
// initialize
internal Converter(IDataReader dataReader)
{
// initialize the internal datareader
this.dataReader = dataReader;
// build our map
_converter = GetMapFunc();
}
// create and map each column to it's respective object
internal T CreateItemFromRow()
{
return _converter(dataReader);
}
}
Mapper
private static IList<T> Map<T>(DbDataReader dr) where T : new()
{
try
{
// initialize our returnable list
List<T> list = new List<T>();
// fire up the lamda mapping
var converter = new Converter<T>(dr);
while (dr.Read())
{
// read in each row, and properly map it to our T object
var obj = converter.CreateItemFromRow();
// add it to our list
list.Add(obj);
}
// reutrn it
return list;
}
catch (Exception ex)
{
// make sure this method returns a default List
return default(List<T>);
}
}
I just don't quite understand where the column to typed object happens in here, so I'd try to do it myself... but I just don;t know where it is.
I know this probably won't help much, but the error I am getting is:
Unable to cast object of type 'System.DBNull' to type 'System.String'.
and it happens on the
internal T CreateItemFromRow()
{
return _converter(dataReader); //<-- Here
}
Note
This does not happen if I wrap the columns in the query itself with an ISNULL(column, ''), but I am sure you can understand that this is surely not a solution
The problem lies in the line convertExp = Expression.Convert(propertyExp, property.PropertyType). You can't expect to convert DbNull value to its equivalent in framework type. This is especially nasty when your type is a value type. One option is to check if the read value from db is DbNull.Value and in case yes, you need to find a compatible value yourself. In some cases people are ok with default values of those types in C#. If you have to do this
property = value == DBNull.Value ? default(T): value;
a generic implementation would look like (as far as the foreach in your converter class goes):
foreach (var column in columns)
{
var property = targetExp.Type.GetProperty(
column.name,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property == null)
continue;
var columnIndexExp = Expression.Constant(column.i);
var propertyExp = Expression.MakeIndex(
paramExp, indexerInfo, new[] { columnIndexExp });
var convertExp = Expression.Condition(
Expression.Equal(
propertyExp,
Expression.Constant(DBNull.Value)),
Expression.Default(property.PropertyType),
Expression.Convert(propertyExp, property.PropertyType));
var bindExp = Expression.Assign(
Expression.Property(targetExp, property), convertExp);
exps.Add(bindExp);
}
Now this does an equivalent of
property = reader[index] == DBNull.Value ? default(T): reader[index];
You could avoid the double lookup of the reader by assigning it to a variable and using its value in the conditional check. So this should be marginally better, but a lil' more complex:
foreach (var column in columns)
{
var property = targetExp.Type.GetProperty(
column.name,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property == null)
continue;
var columnIndexExp = Expression.Constant(column.i);
var cellExp = Expression.MakeIndex(
paramExp, indexerInfo, new[] { columnIndexExp });
var cellValueExp = Expression.Variable(typeof(object), "o7thPropValue");
var convertExp = Expression.Condition(
Expression.Equal(
cellValueExp,
Expression.Constant(DBNull.Value)),
Expression.Default(property.PropertyType),
Expression.Convert(cellValueExp, property.PropertyType));
var cellValueReadExp = Expression.Block(new[] { cellValueExp },
Expression.Assign(cellValueExp, cellExp), convertExp);
var bindExp = Expression.Assign(
Expression.Property(targetExp, property), cellValueReadExp);
exps.Add(bindExp);
}
This does the conditional check this way:
value = reader[index];
property = value == DBNull.Value ? default(T): value;
This is one of the most annoying problems in dealing with datasets in general.
The way I normally get around it is to convert the DBNull value to something more useful, like an actual null or even a blank string in some cases. This can be done in a number of ways, but just recently I've taken to using extension methods.
public static T? GetValueOrNull<T>(this object value) where T : struct
{
return value == null || value == DBNull.Value ? (T?) null : (T) Convert.ChangeType(value, typeof (T));
}
A handy extension method for nullable types, so for example:
int? myInt = DataSet.Tables[0].Rows[0]["DBNullInt"].GetValueOrNull<int>();
Or a more generic one to just convert a DBNull in to a null:
public static object GetValueOrNull(this object value)
{
return value == DBNull.Value ? null : value;
}
string myString DataSet.Tables[0].Rows[0]["DBNullString"].GetValueOrNull();
You'll then get a null string, rather than trying to put a DBNull in to a string.
Hopefully that may help you a little.
As I come across this problem recently
both
Expression.TypeIs(propertyExp,typeof(DBNull));
and
Expression.Equal(propertyExp,Expression.Constant(DBNull.Value));
didn't work for me as they did increase memory allocation (which is my primary concern in this case)
here is the benchmark for both mapper approach compare to Dapper on 10K rows query.
TypeIs
and
Equal
so to fix this problem it came out that an IDataRecord is able to call "IsDBNull" to check whether the column in current reader is DBNull or not
and can be write as expression like
var isReaderDbNull = Expression.Call(paramExp, "IsDBNull", null, readerIndex);
finally, I end up with this solution
and now the performance is acceptable again.
Related
I have provided a dotnetfiddle to show the issue.
I try to copy object from a source that have the same property names and type except some properties that have IEnumerable and target object has
IList using reflection.
public T CopyTo<T>(object src)
where T : new()
{
var targetObj = new T();
//Getting Type of Src
var sourceType = src.GetType();
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty;
var sourcePi = sourceType.GetProperties(flags);
foreach (var property in sourcePi)
{
var pi = targetObj.GetType().GetProperty(property.Name);
if (pi == null || !pi.CanWrite)
continue;
object sourceValue = property.GetValue(src, null);
//var sourceValue = Convert.ChangeType(property.GetValue(src, null), pi.PropertyType);
//this works, but hard wired
if (sourceValue is IEnumerable<string> i)
sourceValue = ((IEnumerable<string>)i).Cast<string>().ToList();
pi.SetValue(targetObj, sourceValue, null);
}
return targetObj;
}
It raises an error:
System.ArgumentException: 'Object of type 'System.String[]' cannot be
converted to type 'System.Collections.Generic.List`1[System.String]'.'
I tried to convert:
var sourceValue = Convert.ChangeType(property.GetValue(src, null), pi.PropertyType);
but also get error
System.InvalidCastException: Object must implement IConvertible.
this issue can't help.
My workaround solution is casting :
sourceValue = ((IEnumerable<string>) sourceValue).Cast<string>().ToList();
The disadvantage is hard wiring the cast to IEnumerable<string>
Is there a better way to copy IEnumerable<T> to IList<T> or any generic collection using reflection.
A simple approach would be to check if IEnumerable<T> is assignable from the source property's type, check if the destination property's type is assignable from List<T>, and if so then assign a new List<T> to the destination property passing the source property's value into the constructor.
public T CopyTo<T>(object src) where T : new()
{
var targetObj = new T();
var sourceType = src.GetType();
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty;
var sourcePi = sourceType.GetProperties(flags);
foreach (var property in sourcePi)
{
var pi = targetObj.GetType().GetProperty(property.Name);
if (pi == null || !pi.CanWrite)
continue;
object sourceValue = property.GetValue(src, null);
//var sourceValue = Convert.ChangeType(property.GetValue(src, null), pi.PropertyType);
//this works, but hard wired
if (property.PropertyType.IsGenericType)
{
Type enumerable = typeof(IEnumerable<>).MakeGenericType(property.PropertyType.GenericTypeArguments);
if (enumerable.IsAssignableFrom(property.PropertyType))
{
Type list = typeof(List<>).MakeGenericType(property.PropertyType.GenericTypeArguments);
if (pi.PropertyType.IsAssignableFrom(list))
{
var pValue = Activator.CreateInstance(list, new[] { sourceValue });
pi.SetValue(targetObj, pValue, null);
}
}
}
else
{
pi.SetValue(targetObj, sourceValue, null);
}
}
return targetObj;
}
It isn't hard to imagine situations where this wouldn't work... for example if the destination type's property is LinkedList<T> or T[] then the properties won't copy. You'd have to modify it further to handle cases like this.
This would also create a new List<T> for the destination object even if the properties on the two objects are both exactly the same type. It isn't clear if that is what you want or not, but it is different from how other properties are copied by this method so it is worth mentioning.
I think you need to add a check of the destination type too.
And then, With the two different type (source and destination),
You can create a function that will handle the conversion/cast.
Something like this:
1. Source IEnumerable<T>, Destination: List<T> => Create New list and put in CTOR
2. Source List<T>, Destination: List<T> => Just Copy
3. Source List<T>, Destination: IEnumerable<T> => Create a list
4. Source IEnumerable<T> ,Destination: IEnumerable<T> => Made a copy?
I'm trying to populate a generic List< T > from another List< U > where the field names match, something like the untested pseudocode below. Where I'm having problems is when T is a string, for instance, which has no parameterless constructor. I've tried adding a string directly to the result object, but this gives me the obvious error -- that a string is not of Type T. Any ideas of how to solve this issue? Thanks for any pointers.
public static List<T> GetObjectList<T, U>(List<U> givenObjects)
{
var result = new List<T>();
//Get the two object types so we can compare them.
Type returnType = typeof(T);
PropertyInfo[] classFieldsOfReturnType = returnType.GetProperties(
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.NonPublic |
BindingFlags.Public);
Type givenType = typeof(U);
PropertyInfo[] classFieldsOfGivenType = givenType.GetProperties(
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.NonPublic |
BindingFlags.Public);
//Go through each object to extract values
foreach (var givenObject in givenObjects)
{
foreach (var field in classFieldsOfReturnType)
{
//Find where names match
var givenTypeField = classFieldsOfGivenType.Where(w => w.Name == field.Name).FirstOrDefault();
if (givenTypeField != null)
{
//Set the value of the given object to the return object
var instance = Activator.CreateInstance<T>();
var value = field.GetValue(givenObject);
PropertyInfo pi = returnType.GetProperty(field.Name);
pi.SetValue(instance, value);
result.Add(instance);
}
}
}
return result;
}
If T is string and you have already created custom code to convert your givenObject to a string, you just need to do an intermediate cast to object to add it to a List<T>:
public static List<T> GetObjectList2<T, U>(List<U> givenObjects) where T : class
{
var result = new List<T>();
if (typeof(T) == typeof(string))
{
foreach (var givenObject in givenObjects)
{
var instance = givenObject.ToString(); // Your custom conversion to string.
result.Add((T)(object)instance);
}
}
else
{
// Proceed as before
}
return result;
}
Incidentally, you are adding an instance of T to result for every property of T that matches a property name in U and for every item in givenObjects. I.e. if givenObjects is a list of length 1 and T is a class with 10 matching properties, result could end up with 10 entries. This looks wrong. Also, you need to watch out for indexed properties.
As an alternative to this approach, consider using Automapper, or serializing your List<U> to JSON with Json.NET then deserializing as a List<T>.
I'm attempting to retrieve the value of a property from an instance of MemberExpression.
Here is what I have so far:
protected override void VisitMember(Context context, MemberExpression node)
{
var propertyInfo = node.Member as PropertyInfo;
if(propertyInfo != null)
{
var v = propertyInfo.GetValue(node.Member , null);
val = Convert.ToString(v);
}
context.State.Append(val);
}
Depending on the approach I take there are two problems: I don't know the expected type (string, int, etc...), and/or I have not been able access the instance from the MemberExpression.
I am writing a small lambda expressions to T-SQL converter. For example (u)=> u.FirstName == u.LastName; would convert to FirstName = 'chuck'. I've almost got it working!
update
I tried the following code:
...
var propertyInfo = node.Member as PropertyInfo;
if(propertyInfo != null)
{
var o = propertyInfo.GetValue(node.Expression, null);
}
...
It did not work. I get the following error:
System.Reflection.TargetException : Object does not match target type.
update 2
This is what I am trying to accomplish:
public static Func<T, object> GetValueGetter<T>(this PropertyInfo propertyInfo)
{
if (typeof(T) != propertyInfo.DeclaringType)
{
throw new ArgumentException();
}
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var property = Expression.Property(instance, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
return (Func<T, object>)Expression.Lambda(convert, instance).Compile();
}
But I do not know T at compile time.
I don't know the expected type (string, int, etc...),
Use Expression.Type
I have not been able access the instance from the MemberExpression
Use MemberExpression.Expression - obviously that's another expression, because you might have:
foo.GetBar(20).ToString().Length
in which case the Length property would be a MemberExpression, but the Expression property would give the MethodCallExpression for ToString.
I have not been able access the instance from the MemberExpression.
In your example u => u.FirstName == "chuck", there is no instance for which to fetch the FirstName property.
I think you actually want the name of the property - which is node.Member.Name (and is "FirstName" in your example). Note that this works for all MemberInfo, not just PropertyInfo, so fields will work as well. (You may want to test anyway, because Events are also member expressions, but don't make sense here.)
I don't know the expected type (string, int, etc...),
The expected type is either PropertyInfo.PropertyType or FieldInfo.FieldType.
Given a type T, is there any way to write something equivalent of
if (typeof(T).ImplementsProperty(MaxValue))
{
return T ?? typeof(T).MaxValue;
}
else
return T;
Note that I don't need a generic type constraint on the class or method, I just need a conditional in the method body. T in this case can be any IComparable. I'm trying to get null numeric/date types to be ordered with the nulls occurring last.
Edit: Sorry there is an error in the above syntax as pointed out by Ray. It should be returning value ?? typeof(T).MaxValue given a T value or something like that. Hope thats clear.
This seems to work for me for nullable, value and reference types:
public T GetSelfOrMaxValue<T>(T value)
{
var t = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
var fi = t.GetField("MaxValue");
if (fi != null && fi.IsStatic && fi.FieldType.Equals(t))
{
return (T)fi.GetValue(null);
}
return value;
}
Is this what you wanted?
Firstly a problem. You can't return T which is a Type Parameter. It would be equivalent to return int which is invalid.
But you can see if T has a MaxValue property and call it if it does. The below code checks for the static property called MaxValue and calls it (and assumes it's an int).
Type type = typeof (T);
var propInfo = type.GetProperty("MaxValue", BindingFlags.Static | BindingFlags.Public);
if (propInfo != null)
return (int)propInfo.GetValue(null, null);
I assume you want something like this:
public static T GetValueOrMax<T>(T value) where T:IComparable
{
if (value != null)
return value;
Type type = typeof (T);
var propInfo = type.GetProperty("MaxValue", BindingFlags.Static | BindingFlags.Public);
if (propInfo != null)
return (T)propInfo.GetValue(null, null);
return value;
}
But this will have it's own problems, if you pass an int in, it will never be null and it will always return value. If you pass a nullable int in, then it won't implement MaxValue (actually you can't pass a nullable int, since it doesn't implement IComparable).
A where clause of where T:class, IComparable may be the most appropriate.
Another option would be to change the check at the beginning to be
if (value != default(T))
return value
But then passing 0 would return the MaxValue not 0, which may not be what you want.
the question is : if i have MyConvertDataRowToEntity(DataRow row )
and I call in with T object from type Parent and inside I call the same function with desendant type Child how should I pass the DataRow parameter ?
The problem is created when Invoke of MakeGenericMethod called.
Did change the type to DataSet , string and String types .
No luck.
(I recognize the children object bu prefix in column names - PrefixDataColumn )
public static T MyConvertDataRowToEntity<T>(DataRow row ) where T : class, new()
{
Type objType = typeof(T);
Type parentObjType = typeof(T);
T obj = Activator.CreateInstance<T>(); //hence the new() contsraint
PropertyInfo propertyGenericType = null;
object childInstance = null;
PropertyInfo property;
string childColumnName = string.Empty ;
foreach (DataColumn column in row.Table.Columns)
{
column.ColumnName = column.ColumnName.Replace("_", "");
string PrefixDataColumn;
if (column.ColumnName.IndexOf(".") > (-1))
{
///gets the prefix that is the same as child entity name
PrefixDataColumn = column.ColumnName.Substring(0, column.ColumnName.IndexOf("."));
///the column name in the child
int length = column.ColumnName.Length - 1;
int start = column.ColumnName.IndexOf(".") + 1;
childColumnName = column.ColumnName.Substring(column.ColumnName.IndexOf(".") + 1);
propertyGenericType = objType.GetProperty(PrefixDataColumn,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy);
parentObjType = objType;
if (propertyGenericType != null)
{
Type childType = propertyGenericType.PropertyType;
objType = childType;
childInstance = Activator.CreateInstance(propertyGenericType.PropertyType);
// get the get method for the property
MethodInfo method = propertyGenericType.GetGetMethod(true);
// get the generic get-method generator
MethodInfo genericHelper = typeof(DataUtil).GetMethod("MyConvertDataRowToEntity", BindingFlags.Public | BindingFlags.Static);
List<Type> signature = new List<Type>();
// first type parameter is type of target object
signature.Add(childType);
//next parameters are real types of method arguments
foreach (ParameterInfo pi in genericHelper.GetParameters())
{
signature.Add(pi.ParameterType);
}
// last parameters are known types of method arguments
signature.AddRange(typeof(T).GetGenericArguments());
// reflection call to the generic get-method generator to generate the type arguments
//MethodInfo constructedHelper = genericHelper.MakeGenericMethod(signature.ToArray());
// reflection call to the generic get-method generator to generate the type arguments
MethodInfo constructedHelper = genericHelper.MakeGenericMethod(childType );
// now call it. The null argument is because it's a static method.
object ret = constructedHelper.Invoke(null, new object[] { method });
// object myObj = method.Invoke(null, row);
//// property.SetValue(obj, MyConvertDataRowToEntity<childInstance>(DataRow row),null);
// childInstance = DataUtil.GetMethod("MyConvertDataRowToEntity").MakeGenericMethod(childType); //MyConvertDataRowToEntity<object>(row);
//childType initializedChild = ;
//property.SetValue(obj, value, null);
//objType = parentObjType;
}
else
{
continue;
}
}
}
return obj;
}
Getting this error :
Object of type 'System.Reflection.RuntimeMethodInfo' cannot be converted to type 'System.Data.DataRow'.
Is there any solution for this ?
p.s.
Narrowed down the code as much as i could.
How can I invoke the Method recursivly with desedant types and pass datarow ?
The reason for the error is because you are calling MyConvertDataRowToEntity<ChildType> but then passing in the getaccessor methodinfo for the property as the parameter instead of a data row containing only those fields.
If you want to continue with the code processing logic you are currently using you would need to construct a new datarow containing the fields you wanted (with the prefix and ".") removed from the start of the column names.
Alternatively you could create a helper method the accepted a column name, the source object and it simply updated the value.
static void UpdateItemProperty<T>(T item, string columnName, object rowValue) {
var prefixColumn=columnName.IndexOf(".")==-1 ? columnName : columnName.Split(".")[0];
var pi = typeof(T).GetProperty(prefixColumn // Add your binding flags);
// if pi==null then there is an error...
if (column.ColumnName.IndexOf(".") == (-1)) { // No Nesting
pi.SetValue(item,rowValue);
return;
}
// Nesting
var child=pi.GetValue(item);
if (child==null) {
// Logic here to get childs type and create an instance then call pi.SetValue with child
}
var remainder=string.Join(',',columnName.Split(".").Skip(1).ToArray());
// make your generic method info for UpdateItemProperty with pi.PropertyType into mi
mi.Invoke(null,new object[] { child,remainder,value };
}