2009-07-09 13 views
10

Come si può mappare un oggetto DataReader in un oggetto di classe utilizzando i generici?C# - IDataReader alla mappatura di oggetti mediante generici

Per esempio ho bisogno di fare quanto segue:

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; 
     } 
    } 

E poi ho bisogno di chiamare questa classe metodo simile al seguente:

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); 
} 

Si noti che, il Mapper - classe dovrebbe essere in grado di mappare l'oggetto di qualsiasi tipo rappresentato da T.

+0

Un suggerimento: leggere in un oggetto IEnumerable con un rendimento restituito. –

+0

// mapping va qui, esattamente quello che ti ho mostrato nella mia risposta, puoi mappare qualsiasi oggetto al lettore di dati (più esattamente: iniettare valori da un IDataReader in un oggetto QUALSIASI TIPO) – Omu

+0

Perché non dovresti usare un ORM dedicato allora? Un micro-ORM come Dapper sembra essere una buona scelta qui. – nawfal

risposta

3

Io uso ValueInjecter per questo

che sto facendo in questo modo:

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

you gonna bisogno di questa ValueInjection per questo lavoro:

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); 
      } 
     } 
    } 
+0

C'è qualche libreria di iniezioni comuni e utili? –

+0

@PavelHodek non c'è una libreria, ma ci sono molti nella soluzione demo principale di valueinjecter e anche nelle pagine di codeplex – Omu

+0

Poche cose. 1) Stai utilizzando la riflessione per impostare il valore, questo è male per le prestazioni. Vai percorso di espressione. 2) Dove sono DataReaderInjection e KnownSourceValueInjection nella libreria? Penso che tu abbia cambiato nome da questa risposta? 3) Sta usando il riflesso dietro le quinte ogni volta che si legge il lettore. Leggi il loop per ottenere i nomi delle proprietà? Spero di no, ma non riesco a convincermi senza vedere la classe KnownSourceValueInjection. – nawfal

2

Beh, non so se si adatta qui, ma potresti utilizzare la parola chiave yield

public static IEnumerable<T> MapObject(IDataReader dr, Func<IDataReader, T> convertFunction) 
     { 
      while (dr.Read()) 
      { 
       yield return convertFunction(dr); 
      } 
     } 
+0

+1 Uso interessante di DI. Sarebbe bello che il tipo T fornisse anche l'implementazione di convertFunction. : D –

+0

Questo è un DI carino e può essere combinato con la soluzione di Omu. –

1

questo sta per essere molto difficile da fare per la ragione che si sono fondamentalmente tentando di mappare due incognite insieme. Nel tuo oggetto generico il tipo è sconosciuto, e nel tuo datareader la tabella è sconosciuta.

Quindi, ciò che suggerirei è creare un tipo di attributo di colonna da allegare alle proprietà dell'entità. E poi guarda attraverso quegli attributi di proprietà e prova a cercare i dati da quegli attributi nel datareader.

Il tuo problema più grande sarà, cosa succede se una delle proprietà non viene trovata nel lettore, o viceversa, una delle colonne nel lettore non si trova nell'entità.

Buona fortuna, ma se vuoi fare qualcosa di simile, probabilmente vuoi un ORM o almeno una specie di implementazione di Active Record.

+0

guarda la mia risposta, non è così difficile :), e non ci sono attributi richiesti – Omu

1

Il modo più semplice che posso immaginare di offendere sarebbe quello di fornire un delegato Func<T,T> per la conversione di ogni colonna e la costruzione del libro.

In alternativa, se si seguono alcune convenzioni, è possibile gestirlo tramite riflessione. Ad esempio, se ogni colonna è mappata a una proprietà nell'oggetto risultante utilizzando lo stesso nome e hai limitato T nel tuo Mapper per fornire una T costruibile, puoi utilizzare reflection per impostare il valore di ogni proprietà sul valore nella colonna corrispondente .

1

che dire seguente

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; 

    } 
} 

Non so se è sintatticamente corretto, ma spero u ottenere l'idea.

+0

È possibile evitare le condizioni if ​​else e fare affidamento sul polimorfismo a condizione che la classe Book stessa implementi alcuni di Func. – nawfal

1

mi sento di raccomandare che usereste AutoMapper per questo.