8

How can I map a DataReader object into a class object by using generics?

For example I need to do the following:

public class Mapper<T>
    {
        public static List<T> MapObject(IDataReader dr)
        {
            List<T> objects = new List<T>();

            while (dr.Read())
            {
                //Mapping goes here...
            }

            return objects;
        }
    }

And later I need to call this class-method like the following:

IDataReder dataReader = DBUtil.Fetchdata("SELECT * FROM Book");

List<Book> bookList = Mapper<Book>.MapObject(dataReder);

foreach (Book b in bookList)
{
     Console.WriteLine(b.ID + ", " + b.BookName);
}

Note that, the Mapper - class should be able to map object of any type represented by T.

Michael Pakhantsov
  • 24,855
  • 6
  • 60
  • 59
user366312
  • 16,949
  • 65
  • 235
  • 452
  • One suggestion - read into an IEnumerable with a yield return. – Daniel A. White Jul 09 '09 at 18:02
  • //mapping goes here, exactly what I've showed you in my answer, you can map any object to the data reader (more exactly: injecting values from an IDataReader into an object ANY TYPE) – Omu Jun 15 '10 at 11:40
  • Why wouldn't you use a dedicated ORM then? A micro-ORM like Dapper seem to be a good fit here. – nawfal Jul 29 '15 at 20:27
  • @nawfal, This was asked in July, 2009. – user366312 Jul 29 '15 at 20:28
  • @BROY Honestly, with comments, answers etc always future visitors are considered too. And it's not like ORMs didnt exist in 2009 :) – nawfal Jul 29 '15 at 20:35
  • @nawfal, then why don't you offer a bounty to update the answer? – user366312 Jul 29 '15 at 20:36
  • @BROY there are tons of questions on the same subject and I'm sure it is answered elsewhere. One such: http://stackoverflow.com/questions/19841120/generic-dbdatareader-to-listt-mapping. As I said, you could use a micro-ORM. Dapper does this. – nawfal Jul 29 '15 at 20:38
  • @nawfal, is that the excuse for not offering a bounty, or, a proof that the question was useless? The link you showed was asked in 2013. – user366312 Jul 29 '15 at 20:40
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/84618/discussion-between-nawfal-and-broy). – nawfal Jul 29 '15 at 20:40
  • Related: http://stackoverflow.com/questions/812034/fastest-way-to-use-reflection-for-converting-datareader-to-list – nawfal Aug 01 '15 at 11:58

10 Answers10

3

I use ValueInjecter for this

I'm doing like this:

 while (dr.Read())
  {
      var o = new User();
      o.InjectFrom<DataReaderInjection>(dr);
      yield return o;
  }

you gonna need this ValueInjection for this to work:

public class DataReaderInjection : KnownSourceValueInjection<IDataReader>
    {
        protected override void Inject(IDataReader source, object target, PropertyDescriptorCollection targetProps)
        {
            for (var i = 0; i < source.FieldCount; i++)
            {
                var activeTarget = targetProps.GetByName(source.GetName(i), true);
                if (activeTarget == null) continue;

                var value = source.GetValue(i);
                if (value == DBNull.Value) continue;

                activeTarget.SetValue(target, value);
            }
        }
    }
Omu
  • 69,856
  • 92
  • 277
  • 407
  • Is somewhere any library of common and useful injections? – Pavel Hodek Dec 14 '12 at 14:44
  • @PavelHodek there isn't a library but there are many in valueinjecter's main demo solution and also in the codeplex pages – Omu Dec 14 '12 at 15:38
  • Few things. 1) You're using reflection to set value, this is bad for performance. Go expression route. 2) Where is DataReaderInjection and KnownSourceValueInjection in your library? I think you changed names since this answer? 3) Is it using reflection behind the scenes every time in the while reader.Read loop to get property names? I hope no, but cant convince myself without seeing KnownSourceValueInjection class. – nawfal Jul 29 '15 at 14:55
  • 4) This is minor but still - you're not assigning to property when it is null in database (DBNull case), but what if some value is assigned to that property when new-ing in the constructor, like "var o = new User()"? In that case the user you return will be different from what is actually present in the db. – nawfal Jul 29 '15 at 15:10
2

Well, i don't know if it fits here, but you could be using the yield keyword

public static IEnumerable<T> MapObject(IDataReader dr, Func<IDataReader, T> convertFunction)
        {
            while (dr.Read())
            {
                yield return convertFunction(dr);
            }
        }
Jhonny D. Cano -Leftware-
  • 17,663
  • 14
  • 81
  • 103
1

You could use this LateBinder class I wrote: http://codecube.net/2008/12/new-latebinder/.

I wrote another post with usage: http://codecube.net/2008/12/using-the-latebinder/

Joel Martinez
  • 46,929
  • 26
  • 130
  • 185
1

This is going to be very hard to do for the reason that you are basically trying to map two unknowns together. In your generic object the type is unknown, and in your datareader the table is unknown.

So what I would suggest is you create some kind of column attribute to attach to the properties of you entity. And then look through those property attributes and try to look up the data from those attributes in the datareader.

Your biggest problem is going to be, what happens if one of the properties isn't found in the reader, or vice-versa, one of the columns in the reader isn't found in the entity.

Good luck, but if you want to do something like this, you probably want a ORM or at the very least some kind of Active Record implementation.

Nick Berardi
  • 54,393
  • 15
  • 113
  • 135
1

The easiest way I can think of offhand would be to supply a Func<T,T> delegate for converting each column and constructing your book.

Alternatively, if you followed some conventions, you could potentially handle this via reflection. For example, if each column mapped to a property in the resulting object using the same name, and you restricted T in your Mapper to providing a constructable T, you could use reflection to set the value of each property to the value in the corresponding column.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
1

I don't think you'll be able to get around defining the relationship between fields in some form. Take a look at this article and pay particular attention to how the mapping is defined, it may work for you.

http://www.c-sharpcorner.com/UploadFile/rmcochran/elegant_dal05212006130957PM/elegant_dal.aspx

ramnik
  • 545
  • 2
  • 11
1

what about following

abstract class DataMapper
{
    abstract public object Map(IDataReader);
}

class BookMapper : DataMapper
{
   override public object Map(IDataReader reader)
   {
       ///some mapping stuff
       return book;
   }
}

public class Mapper<T>
{
    public static List<T> MapObject(IDataReader dr)
    {
        List<T> objects = new List<T>();
        DataMapper myMapper = getMapperFor(T);
        while (dr.Read())
        {
            objects.Add((T)myMapper(dr));
        }

        return objects;
    }

    private DataMapper getMapperFor(T myType)
    {
       //switch case or if or whatever
       ...
       if(T is Book) return bookMapper;

    }
}

Don't know if it is syntactically correct, but I hope u get the idea.

nWorx
  • 2,145
  • 16
  • 37
  • You could avoid the if else conditions and rely on polymorphism provided the Book class itself implements some Func. – nawfal Jul 29 '15 at 15:01
1

What about using Fluent Ado.net ?

Giorgi
  • 30,270
  • 13
  • 89
  • 125
1

Have a look at http://CapriSoft.CodePlex.com

zackevans
  • 11
  • 1
1

I would recommend that you'd use AutoMapper for this.

Robin Orheden
  • 2,714
  • 23
  • 24