[Data Access] ORM 原理 (3) : 處理列舉型別 (Enumeration Type)

我們在原理 (2) 中處理了許多內建的型別,不過還有幾種比較棘手的型別,其中一個就是列舉 (enumeration),列舉也是一種實值型別,只是它大多用來作為限制常數的用途 (使用有意義的指令取代數字),而且它不能用在泛型,所以 where 等於是不能用 (雖然有替代方案)。

我們在原理 (2) 中處理了許多內建的型別,不過還有幾種比較棘手的型別,其中一個就是列舉 (enumeration),列舉也是一種實值型別,只是它大多用來作為限制常數的用途 (使用有意義的指令取代數字),而且它不能用在泛型,所以 where 等於是不能用 (雖然有替代方案)。

因此,我們在原理 (2) 中所用的 ITypeConverter 就無法沿用,我們必須要額外給定一個方法,傳入列舉型別,才能夠順利將轉型成列舉,所以我們新寫了一個 EnumConverter,但不實作原有的 Convert(),而是加一個新的 Convert():

public class EnumConverter : ITypeConverter
{
    public object Convert(object ValueToConvert)
    {
        throw new NotImplementedException();
    }
    public object Convert(Type EnumType, object ValueToConvert)
    {
        if (!EnumType.IsEnum)
            throw new InvalidOperationException("ERROR_TYPE_IS_NOT_ENUMERATION");
        return System.Convert.ChangeType(Enum.Parse(EnumType, ValueToConvert.ToString()), EnumType);
    }
}

然後,新增一個 Product 類別,以及等等會用到的 ReorderLevel 列舉:

public enum ReorderLevel
{
    None    = 0,
    Lowest  = 5,
    Lower   = 10,
    Medium  = 15,
    More    = 20,
    High    = 25,
    Often   = 30
}
public class Product
{
    public string ProductID { get; set; }
    public string ProductName { get; set; }
    public ReorderLevel ReorderLevel { get; set; }
}

接著修改主程式,這次要取的資料是 Products 表格的 ProductID, ProductName 以及 ReorderLevel 三個欄位:

class ProgramStep3
{
    static void Main(string[] args)
    {
        // step 2. handling data type convert.
        SqlConnection db = new SqlConnection("initial catalog=Northwind; integrated security=SSPI");
        SqlCommand dbcmd = new SqlCommand(@"SELECT ProductID, ProductName, ReorderLevel FROM Products", db);
        List<Product> products = new List<Product>();
        db.Open();
        SqlDataReader reader = dbcmd.ExecuteReader(CommandBehavior.CloseConnection);
        while (reader.Read())
        {
            Product product = new Product();
            for (int i = 0; i < reader.FieldCount; i++)
            {
                // TODO: implement data binding to object with enumeration.                
            }
            products.Add(product);
        }
        reader.Close();
        db.Close();
        foreach (Product product in products)
        {
            Console.WriteLine("id: {0}, name: {1}, level: {2}",
                product.ProductID, product.ProductName,
                Enum.GetName(typeof(ReorderLevel), product.ReorderLevel));
        }
        Console.WriteLine("");
        Console.WriteLine("Press ENTER to exit.");
        Console.ReadLine();
    }
}

再修改一下 TypeConverterFactory:

public class TypeConverterFactory
{
    public static ITypeConverter GetConvertType<T>()
    {
        if (typeof(T) == typeof(int))
            return (new IntegerConverter());
        if (typeof(T) == typeof(long))
            return (new LongConverter());
        if (typeof(T) == typeof(short))
            return (new ShortConverter());
        if (typeof(T) == typeof(float))
            return (new FloatConverter());
        if (typeof(T) == typeof(double))
            return (new DoubleConverter());
        if (typeof(T) == typeof(decimal))
            return (new DecimalConverter());
        if (typeof(T) == typeof(bool))
            return (new BooleanConverter());
        if (typeof(T) == typeof(char))
            return (new CharConverter());
        if (typeof(T) == typeof(string))
            return (new StringConverter()); 
        if (typeof(T).IsEnum)
            return (new EnumConverter());
        return null;
    }
    public static ITypeConverter GetConvertType(Type T)
    {
        if (T == typeof(int))
            return (new IntegerConverter());
        if (T == typeof(long))
            return (new LongConverter());
        if (T == typeof(short))
            return (new ShortConverter());
        if (T == typeof(float))
            return (new FloatConverter());
        if (T == typeof(double))
            return (new DoubleConverter());
        if (T == typeof(decimal))
            return (new DecimalConverter());
        if (T == typeof(bool))
            return (new BooleanConverter());
        if (T == typeof(char))
            return (new CharConverter());
        if (T == typeof(string))
            return (new StringConverter()); 
        if (T.IsEnum)
            return (new EnumConverter());
        return null;
    }
}

現在,我們就可以在 TODO 中加入資料繫結的功能了:

PropertyInfo property = product.GetType().GetProperty(reader.GetName(i));
Type propType = property.PropertyType;
TypeConverters.ITypeConverter typeConverter = TypeConverters.TypeConverterFactory.GetConvertType(propType);
if (!propType.IsEnum)
{
    property.SetValue(product,
        Convert.ChangeType(typeConverter.Convert(reader.GetValue(i)), propType), null);
}
else
{
    TypeConverters.EnumConverter converter = typeConverter as TypeConverters.EnumConverter;
    property.SetValue(product,
        Convert.ChangeType(converter.Convert(propType, reader.GetValue(i)), propType), null);
}

 由於 ITypeConverter 沒有 Convert(Type, object) 這個函式,只有 EnumConverter 才有,所以我們要先判斷屬性的型別是否是列舉,如果是,就用 EnumConverter 處理,否則仍用一般的作法處理。

 試跑程式,會得到 ReorderLevel 的列舉值的名稱。