2015-05-14 17 views
13

Ho il seguente codice che è in grado di mappare Reader in oggetti semplici. Il problema è che se l'oggetto è composto non riesce a mappare. Io non sono in grado di eseguire la ricorsione verificando la proprietà se si tratta di una classe stessaMappatore oggetti relazionale generico C#

prop.PropertyType.IsClass come tipo è necessaria per chiamare DataReaderMapper(). Qualche idea su come questo possa essere raggiunto o qualche altro approccio? Inoltre, attualmente non desidero utilizzare alcun ORM.

public static class MapperHelper 
{ 

    /// <summary> 
    /// extension Method for Reader :Maps reader to type defined 
    /// </summary> 
    /// <typeparam name="T">Generic type:Model Class Type</typeparam> 
    /// <param name="dataReader">this :current Reader</param> 
    /// <returns>List of Objects</returns> 
    public static IEnumerable<T> DataReaderMapper<T>(this IDataReader dataReader)where T : class, new() 
    { 
     T obj = default(T); 

     //optimized taken out of both foreach and while loop 
     PropertyInfo[] PropertyInfo; 
     var temp = typeof(T); 
     PropertyInfo = temp.GetProperties(); 

     while (dataReader.Read()) 
     { 
      obj = new T(); 

      foreach (PropertyInfo prop in PropertyInfo) 
      { 
       if (DataConverterHelper.ColumnExists(dataReader,prop.Name) && !dataReader.IsDBNull(prop.Name)) 
       { 
        prop.SetValue(obj, dataReader[prop.Name], null); 
       } 
      } 
      yield return obj; 

     } 
    } 
} 
+1

Si noti che la propria strategia non assegna nulla alla proprietà quando il valore db è nullo (caso DBNull) è leggermente imperfetto. Ad es. Supponiamo che tu abbia un valore assegnato ad alcune proprietà pubbliche nel costruttore predefinito, come "public T() {P = someValue;}" Ora se la proprietà "P" è DbNull in db e non stai assegnando nulla ad essa nella tua mappatura codice per gestire i casi null, quindi il mapper restituisce una T dove P = someValue (assegnato nel costruttore) mentre in db è DBNull. Questi sono casi strani ma ancora per motivi di purezza ... – nawfal

risposta

9

Non eseguire DataReaderMapper ricorsivo. Basta rendere la parte cartografica ricorsiva:

static void Assign(IDataReader reader, object instance) { 
     foreach (PropertyInfo prop in PropertyInfo) 
     { 
      if (IsValue(prop)) 
      { 
       prop.SetValue(obj, dataReader[prop.Name], null); 
      } 
      else if (IsClass(prop)) { 
       var subInstance = Activator.CreateInstance(prop.PropertyType); 
       prop.SetValue(obj, subInstance, null); 
       Assign(subInstance, dataReader); 
      } 
     } 
} 

Così. Questo inizializza ricorsivamente tutte le proprietà del tipo di classe con istanze predefinite e assegna loro valori di lettore di dati.

Il codice è chiaramente semplificato. Ho elided alcune delle tue cose e IsValue/IsClass sono lasciati a implementare a tuo piacimento. Inoltre, probabilmente si desidera utilizzare uno schema di denominazione in modo che a.b.c come nome di colonna esegua il mapping su tale proprietà. È fattibile passando il prefisso del nome corrente come parametro a Assign.

Inoltre, non è necessario che DataReaderMapper sia generico. Lo sto dicendo perché hai lottato con quello. Sostituire typeof(T) con un parametro Type e restituire un IEnumerable<object>. Quindi chiama Cast<T>() sul risultato del tuo metodo. Quindi vedi che questo algoritmo in linea di principio può funzionare senza generici.

1

La mia preferenza sarebbe quella di lasciare il pesante sollevamento al codice di chiamata. Questo evita relativamente lento ricorsione, e permette di costruire oggetti in cui i nomi dei campi non si allineano esattamente o che non hanno costruttori di default:

public static class MapperHelper 
{ 
    public static IEnumerable<T> DataReaderMapper<T>(this IDataReader dataReader, Func<IDataRecord, T> map) 
    { 
     while (dataReader.Read()) 
     { 
      yield return map(dataReader); 
     } 
    } 

Poi vorrei spostare il codice di proprietà esistente per un metodo che potrebbe essere passato qui come un'implementazione di default:

public static T DefaultMapper<T>(IDataRecord record) where T : class, new() 
    { 
     //This is now effectively inside the while loop, 
     // but .Net caches the expensive recursive calls for you 
     PropertyInfo[] PropertyInfo; 
     var temp = typeof(T); 
     PropertyInfo = temp.GetProperties(); 

     obj = new T(); 
     foreach (PropertyInfo prop in PropertyInfo) 
     { 
      if (DataConverterHelper.ColumnExists(dataReader,prop.Name) && !dataReader.IsDBNull(prop.Name)) 
      { 
       prop.SetValue(obj, dataReader[prop.Name], null); 
      } 
     } 
     return obj; 
    } 
} 

si potrebbe chiamare con il mapper di default in questo modo:

foreach (var record in myDataReader.DataReaderMapper<SomeType>(MapperHelper.DefaultMapper)) 
{ 
    //... 
} 

Naturalmente questo sarà ancora fa su tipi di compositi complessi, ma non vedo come ci si possa aspettare che un generico mapper abbia successo in quello scenario ... se il proprio tipo interno ha proprietà da riempire, non c'è un buon modo per specificare il nome corrispondente. Questo ti permette di scrivere velocemente il proprio mapper, se è necessario:

foreach (var record in myDataRecord.DataReaderMapper<SomeType>(r => { 
    //complex mapping goes here 
    SomeType result = new SomeType() { 
     field1 = r["field1"], 
     field2 = new OtherType() { 
      subField = r["subField"], 
      otherField = r["otherField"] 
     } 
    } 
    return result;  
}) 
{ 
    //... 
} 

E naturalmente si può sempre creare la logica per cui la conversione in un metodo che potrebbe invece passare per nome.

Problemi correlati