Conversion problem with Expression Trees

c# expression-trees

Question

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?

  1. Attempt to fix this in the code? (If so, any pointers?)
  2. 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, property);
    
            // 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 :)

Accepted Answer

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


Popular Answer

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.




Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why