2015-05-14 36 views
13

我有以下代碼能夠將Reader映射到簡單對象。麻煩的是如果對象是合成的,它無法映射。我無法通過檢查屬性來執行遞歸,如果它是一類本身與複合C#對象映射器的通用關係

prop.PropertyType.IsClass 的類型,需要調用DataReaderMapper()。任何想法如何實現這一目標或其他方法?另外,目前我不希望使用任何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

請注意,當db值爲空(DBNull大小寫)時,您的策略不會將任何內容分配給屬性,這是略有缺陷的。對於例如假設你有一些值在默認構造函數中被賦值給一些公共屬性,比如「public T(){P = someValue;}」現在,如果屬性「P」是db中的DbNull,並且你沒有在映射中爲它分配任何東西代碼來處理空情況,然後映射器返回一個T,其中P = someValue(在構造函數中分配),而在db中它是DBNull。這些都是奇怪的情況,但仍然是爲了純潔... – nawfal

回答

9

不要讓DataReaderMapper遞歸。只需使映射部分遞歸:

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

就像那樣。這將遞歸地初始化所有具有默認構造實例的類類型屬性,併爲它們分配數據讀取器值。

代碼顯然簡化了。我忽略了一些你的東西,並且IsValue/IsClass被留下去實現你的喜好。此外,您可能想要使用命名機制,以便a.b.c作爲列名映射到該屬性。通過將當前名稱前綴作爲參數傳遞給Assign,這是可行的。

進一步說明,DataReaderMapper是通用的不是必需的。我說這是因爲你爲此而掙扎。將typeof(T)替換爲Type參數並返回IEnumerable<object>。然後在您的方法結果上致電Cast<T>()。所以你看到這個算法原則上可以在沒有泛型的情況下工作。

1

我的首選是將重要任務留給調用代碼。這避免了相對緩慢的遞歸,並允許你建立的對象,其中的字段名稱不完全一致或沒有默認的構造函數:

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

然後我將你的現有屬性代碼移到了一種方法,在這裏可以通過一個默認的實現:

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

當然,這仍然會發:

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

你可以使用默認的映射器這樣叫它如何處理複雜的複合類型,但我沒有看到你如何期望任何通用映射器能夠在這種情況下成功......如果你的內部類型本身有要填充的屬性,沒有好方法來指定匹配的名稱。這可以讓你快速編寫自己的映射,如果你需要:

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;  
}) 
{ 
    //... 
} 

當然,你總是可以建立該轉換成可能不是按名稱傳遞方法的邏輯。