1

An Extension to this question passing static reflection information to static generic methods

Im trying to create a Generic Type Reader as i do a lot of data access using Classes and i'm trying to create a fairly generic method that allows to read data without much code.

the part of the code that does most of the reading looks like this

 public static T Read<T>(string field,IDataRecord data )
    {
        Type type1 = typeof (T);
        try
        {
            if (type1 == typeof( String ))
            {
                return (T)Convert.ChangeType( readString( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( int? ))
            {
                return (T)Convert.ChangeType( readIntN( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( Guid? ))
            {
                return (T)Convert.ChangeType( readGuidN( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( double? ))
            {
                return (T)Convert.ChangeType( readDoubleN( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( decimal? ))
            {
                var res = readDecimalN(data[field].ToString());
                return (T)Convert.ChangeType( res, typeof( T ) );
            }
            if (type1 == typeof( float? ))
            {
                return (T)Convert.ChangeType( readFloatN( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( bool? ))
            {
                return (T)Convert.ChangeType( readBoolN( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( DateTime? ))
            {
                return (T)Convert.ChangeType( readDatetimeN( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( int ))
            {
                return (T)Convert.ChangeType( readInt( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( long? ))
            {
                return (T)Convert.ChangeType( readLongN( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( long ))
            {
                return (T)Convert.ChangeType( readLong( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( Guid ))
            {
                return (T)Convert.ChangeType(readGuid( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( double ))
            {
                return (T)Convert.ChangeType( readDouble( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( decimal ))
            {
                return (T)Convert.ChangeType( readDecimal( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( float ) || type1 == typeof( Single ))
            {
                return (T)Convert.ChangeType( readFloat( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( bool ))
            {
                return (T)Convert.ChangeType( readBool( data[field].ToString() ), typeof( T ) );
            }
            if (type1 == typeof( DateTime ))
            {
                return (T)Convert.ChangeType( readDatetime( data[field].ToString() ), typeof( T ) );
            }
        }
        catch (Exception)
        {
            throw;
        }
        throw new Exception(String.Format("Data Type Not Supported: {0}", type1));
    }

However this is throwing an Invalid Cast Exception.

the readXXX methods are working fine the issue is occuring in each of the return statements

i have also tried using

public static T SafeConvert<T>(string s, T defaultValue)
{
if ( string.IsNullOrEmpty(s) )
    return defaultValue;
return (T)Convert.ChangeType(s, typeof(T));
} 

but it is still failing

Edit:

the method is being called via

private static List<T> GetItems<T>(IDataReader reader)
    {
        var items = new List<T>();
        while (reader.Read())
        {
            Type type1 = typeof (T);
            var item = (T) Activator.CreateInstance(typeof (T), new object[] {});
            foreach (PropertyInfo info in type1.GetProperties())
            {
                int written = 0;
                if (info.CanWrite)
                {
                    #region

                    try
                    {
                        Type dataType = info.PropertyType;
                        MethodInfo method = typeof (DataReader).GetMethod("Read",BindingFlags.Static | BindingFlags.Public);
                        MethodInfo generic = method.MakeGenericMethod(dataType);
                        var t = generic.Invoke(null, new object[] {info.Name, reader});
                        info.SetValue( item, t );

More...

People seem to be asking what i use this for, and ultimately it allows me to in one line create an ienumerable that is read from any source be it CSV or SQL by passing a file Location or sql query i.e.

//returns Ienumerable<MyClass>
var list = Reader.ReadSql<MyClass>(DataBases.Test,"select * from TestTable where someAttribute  = 1");
// also returns Ienumerable MyClass
var list2 = Readre.ReadCsv<MyClass>(@"c:\file1.csv",","); 

I have it running now but it requires repetiiton of the long list of if dataType == typeof(string) in each implementation i was hoping to refactor that into the single generic Read method but am having trouble with the Convert

Community
  • 1
  • 1
RoughPlace
  • 1,111
  • 1
  • 13
  • 23
  • 2
    You've posted a *lot* of code, but haven't showed how you're calling the method, or what the values are. That makes it very hard to help you. Additionally, your try/catch block is pointless - you're just rethrowing the original exception. – Jon Skeet Mar 27 '13 at 18:50
  • You didn't say, but I'm guessing the exception is thrown for types, such as Guid, that don't implement IConvertable. You'll have to call the constructor for those types. – Michael Sallmen Mar 27 '13 at 18:52
  • hi jon the call is made in the previous question as linked at the top, i will add further detail to this question( and yes i'm aware the try catch is unnecessary using it for debugging currently) – RoughPlace Mar 27 '13 at 18:52
  • Hmmm...it looks a bit like you might be trying to make a "type-safe" version of DataTable...is that accurate? – JerKimball Mar 27 '13 at 19:09
  • @Yakyb, if I'm reading this correctly, you are trying to implement serialization by hand? Why not use the built in C# serialization methods/interface? – IdeaHat Mar 27 '13 at 19:09
  • I don't think `Read` will handle custom types or generics. – Dustin Kingen Mar 27 '13 at 19:19

2 Answers2

0

Your updated question says it works, but you want to refactor. Well, you can!

Dictionary<Type, Func<string, IDataRecord, object>> converters_ = new Dictionary<Type, Func<string, IDataRecord, object>>();

converters_[typeof(bool)] = (s) => { return readBoolN( data[s].ToString() ); };
// repeat for other types:

Then

 public static T Read<T>(string field,IDataRecord data )
 {
     return (T)converters_[typeof(T)](field, data);
 }
Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
0
  1. You invoked GetMethod without a Binder, how can you get the expected one of generics?

  2. Read<T> would be invoked without an argument can used to infer the type, are you expecting that everytime when you invoke it with a type parameter?

  3. If 2 was what you expected, then what's different from just calling GetXXX method?

I'm unable to follow your original design, but consider the following code:

public static partial class DataReaderExtensions {
    /// <summary>
    /// <para>Copy data to target object</para>
    /// <para>Class which implements IDataRecord usually also implements IDataReader</para>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="data"></param>
    /// <param name="target"></param>
    /// <returns>the count of field or property copied</returns>
    public static int CopyTo<T>(this IDataRecord data, T target) {
        return (
            from column in
                Enumerable.Range(0, data.FieldCount).Select(
                    (x, i) => new {
                        DataType=data.GetFieldType(i),
                        ColumnName=data.GetName(i)
                    }
                    )
            let type=target.GetType()
            from member in type.GetMembers()
            let typeMember=
                member is PropertyInfo
                    ?(member as PropertyInfo).PropertyType
                    :member is FieldInfo
                        ?(member as FieldInfo).FieldType
                        :default(MemberInfo)
            where typeMember==column.DataType
            let name=member.Name
            where name==column.ColumnName
            let invokeAttr=
                BindingFlags.SetProperty|BindingFlags.SetField|
                BindingFlags.NonPublic|BindingFlags.Public|
                BindingFlags.Instance
            select type.InvokeMember(name, invokeAttr, default(Binder), target, new[] { data[name] })
            ).Count();
    }
}

You would copy the data directly to a instance of a custom type by statement such like:

reader.CopyTo(myObject); 

It maps column name with field/property automatically, regardless public/non-public; and finally returns the count of copied elements.

Ken Kin
  • 4,503
  • 3
  • 38
  • 76