Conversion problem with Expression Trees
I have an expression tree function from a previous SO question. It basically allows the conversion of a data row into a specific class.
This code works fine, unless you're dealing with data types that can be bigger or smaller (eg. Int32/Int64).
The code throws an invalid cast exception when going from an Int64
to an Int32
when the value would fit in an Int32
(eg. numbers in the 3000).
Should I?
- Attempt to fix this in the code? (If so, any pointers?)
Leave the code as it is.
private Func<SqlDataReader, T> getExpressionDelegate<T>() { // hang on to row[string] property var indexerProperty = typeof(SqlDataReader).GetProperty("Item", new[] { typeof(string) }); // list of statements in our dynamic method var statements = new List<Expression>(); // store instance for setting of properties ParameterExpression instanceParameter = Expression.Variable(typeof(T)); ParameterExpression sqlDataReaderParameter = Expression.Parameter(typeof(SqlDataReader)); // create and assign new T to variable: var instance = new T(); BinaryExpression createInstance = Expression.Assign(instanceParameter, Expression.New(typeof(T))); statements.Add(createInstance); foreach (var property in typeof(T).GetProperties()) { // instance.MyProperty MemberExpression getProperty = Expression.Property(instanceParameter, pro开发者_如何学运维perty); // row[property] -- NOTE: this assumes column names are the same as PropertyInfo names on T IndexExpression readValue = Expression.MakeIndex(sqlDataReaderParameter, indexerProperty, new[] { Expression.Constant(property.Name) }); // instance.MyProperty = row[property] BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(readValue, property.PropertyType)); statements.Add(assignProperty); } var returnStatement = instanceParameter; statements.Add(returnStatement); var body = Expression.Block(instanceParameter.Type, new[] { instanceParameter }, statements.ToArray()); var lambda = Expression.Lambda<Func<SqlDataReader, T>>(body, sqlDataReaderParameter); // cache me! return lambda.Compile(); }
Update:
I have now given up and decided it is not worth it. From the comments below, I got as far as:
if (readValue.Type != property.PropertyType)
{
BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(Expression.Call(property.PropertyType, "Parse", null, new Expression[] { Expression.ConvertChecked(readValue, typeof(string)) }), property.PropertyType));
statements.Add(assignProperty);
}
else
{
// instance.MyProperty = row[property]
BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(readValue, property.PropertyType));
statements.Add(assignProperty);
}
I don't think I was too far off, feel free to finish it and post the answer if you figure it out :)
You could try to fix it by "convert checked" before assigning i.e. using Expression.ConvertChecked
on the value instead of Expression.Convert
.
Couldn't try it right now but this should take care of the case you describe...
EDIT - as per comment this could be a boxing issue:
In this case you could try using Expression.TypeAs
or Expression.Unbox
for the conversion or use Expression.Call
for calling a method to do the conversion... an example for using Call
can be found at http://msdn.microsoft.com/en-us/library/bb349020.aspx
What you're trying to build is actually much more complicated if you want to support 100% of the primitives in .NET and SQL.
If you don't care about some of the edge cases (nullable types, enums, byte arrays, etc), two tips to get you 90% there:
Don't use the indexer on IDataRecord, it returns an object and the boxing/unboxing will kill performance. Instead, notice that IDataRecord has Get[typeName] methods on it. These exist for all .NET primitive types (note: it's GetFloat, not GetSingle, huge annoyance).
You can use IDataRecord.GetFieldType to figure out which Get method you need to call for a given column. Once you have that, you can use Expression.Convert to coerce the DB column type to the target property's type (if they're different). This will fail for some of the edge cases I listed above, for those you need custom logic.
精彩评论