[Reflection][C# .NET]利用反射來簡化ADO.NET的型別綁定

簡介反射與如何利用反射來簡化ADO.NET的型別綁定

前言

反射(Reflection),雖然是很久以前就已經有的概念,且在微軟的C#.NET中已經有做好相關的組件System.Reflection,開發者可以很容易地去應用反射的概念,如果你想做下面的事情的話:

當我們自定義完一個類別(class),你要怎麼在執行階段取得類別的

  • 型別(Type)
  • 屬性(Attribute)資訊(PropertyInfo、FieldInfo or MemberInfo)
  • 是否有掛標籤(CustomAttribute)

或者當我們在Model裡面定義完一個方法,你要怎麼在執行階段取得方法的

  • 內容(MethodInfo)
  • 參數(MethodInfo.GetGenericArguments)
  • 回傳值內容(MethodInfo.ReturnParamter)
  • 回傳值型別(MethodInfo.ReturnType)
  • etc...

只是不知怎麼的,待過的公司專案幾乎沒有用到這個概念,但它真的很好用,搭配泛型可以把很多東西提取出來共用,可以省下非常多的功夫。

應用於ADO.NET

最明顯可以寫成共用的就是ADO.NET,以前在使用ADO.NET存取資料庫的時候,不外乎就是SqlConnection連線之後,透過SqlCommand下指令,再透過SqlDataAdapter的Fill方法,把資料填入DataTable或是用DataReader來讀取資料再塞到Model裡面。

DataTable與Model的Mapping

DataTable如果要轉換成Model List通常做法會是這樣。

//傳入DataTable物件,回傳Model List
public List<DataClass> GetModelList(DataTable dt)
{
    var result = new List<DataClass>();
    //一筆一筆透過欄位名稱與Model綁定
    foreach(var row in dt.Rows)
    {
        var element = new DataClass();
        element.PropA = row["PropA"].ToString();
        element.PropB = row["PropB"].ToString();
        //etc..
        result.Add(element);
    }
    return result;
}

相當簡單易懂,Datable進來就一個一個綁定到Model裡面,非常適合新人來閱讀還有撰寫,不過寫久了就覺得感覺好流水帳,而且element.XXX = row.Column["XXX"].ToString(),這種動作欄位有幾個就要重複幾次,難道沒有更好的做法?有的,請看下方。

public List<DataClass> GetModelList(DataTable dt)
{
    //一行解決
    return dt.MapToModelList<DataClass>();
}
//DataTable與Model List轉換
//並應用泛型來使所有物件都可以使用這方法
public static List<T> MapToModelList<T>(this DataTable dataTable)
{
    var result = new List<T>();
    foreach (DataRow dr in dataTable.Rows)
    {
        result.Add(dr.MapToModel<T>());
    }
    return result;
}

//DataRow與Model轉換
private static T MapToModel<T>(this DataRow dataRow)
{
    //先建立一個預設回傳值
    var result = Activator.CreateInstance<T>();
    //透過Reflection取得型別與屬性清單
    foreach (var property in typeof(T).GetProperties())
    {
        //透過Reflection綁定值
        property.SetValue(result, dataRow[property.Name].ToString());
    }
    return result;
}

感覺門檻高了一點,但是其實並不難,而且寫完之後可以打通你的任督二脈,之後如果有DataTable要轉Model List的,只要寫一行就好了。

DataReader與Model的Mapping

類似作法也會出現在DataReader與Model List的轉換,比較傳統的作法會是這樣。

//傳入DataReader物件,回傳Model List
private static List<DataClass> GetModelList(DbDataReader dataReader)
{
    var result = new List<DataClass>();
    while (dataReader.Read())
    {
        var element = new DataClass();
        element.PropA = dataReader["PropA"].ToString();
        element.PropB = dataReader["PropB"].ToString();
        //etc...
        result.Add(element);
    }

    return result;
}

應用類似的概念,修改成

//DataReader與Model List的轉換
//並應用泛型來使所有物件都可以使用這方法
public List<T> MapToModelList<T>(DbDataReader dataReader)
{
    var result = new List<T>();
    while (dataReader.Read())
    {
        result.Add(dataReader.MapToModel<T>());
    }
    return result;
}
//DataReader與Model的轉換
private static T MapToModel<T>(this DbDataReader dataReader)
{
    //先建立一個預設回傳值
    var result = Activator.CreateInstance<T>();
    //透過Reflection取得型別與屬性清單
    foreach (var property in typeof(T).GetProperties())
    {
        //透過Reflection綁定值
        property.SetValue(result, dataReader[property.Name]);
    }
    return result;
}

結語

以上兩種做法都只是簡單實作一下,實際上直接呼叫這兩個方法會出現一些例外,例如資料庫欄位名稱不正確或者值的型態需要轉換等等,我有做了一個小小的套件放在Github上,這套件有解決了某些例外情形,例如屬性與欄位型別之間的判定以及如果屬性與欄位的名稱不同,可以透過Column Attribute來設定資料表欄位的名稱,希望提供給各位參考參考。 SimpleObjectMapper 應用反射的技巧,就可以省下很多時間來做更有意義的事情,但其實我這樣做的初衷很簡單,就是,所以懶惰是人類進步的原動力,這個觀點真的是對極了。

參考來源

Reflection
SimpleObjectMapper