使用泛型和反射,打造我们的完美实体基类

 

 

背景

 

我在开发的过程中,实体和数据库的访问是一直要接触和编写的代码,考虑到团队中初学者比较多,我一直希望有一种方式可以改善目前编码的困境:

ADO.Net的代码对于初学者来讲比较麻烦,需要他们更多的对ADO.Net有所了解。

将数据库的值为实体属性赋值是一个比较繁琐的过程,程序员容易厌倦这个重复工作,或数据类型,或属性名称的赋值错误。

对于一对多的实体模型开发中,程序员很容易的将一对多的实体对象相互的关联错误。

 

我们当然可以使用一些自动化的工具来辅助,或者采用类似Hibernate的框架或VS2008SP1的一些新功能来实现,但我们又需要在我们的实体中有些更灵活的控制。

 

因此作为开发团队的技术管理人员,我必须要设计一种更好的模式来解决以上的事项。

 

正文中,我们将逐步的阐述我的设计思路。

 

 

 

一、打造DataProvider

 

ADO.Net的封装已经有很多的实现了,但我总感觉那些实现还是没有透明化使用者对ADO.Net的了解。比如说很多人推崇的Enterprise Library的DataAccess,我认为就是封装不够彻底。我理想中封装彻底的ADO.Net对象是,使用者不需要(或尽可能的少)了解任何,而DataAccess还是需要使用者直接的处理很多ADO.Net的对象。而我需要的ADO.Net的封装希望使用者,仅给予SQL命令,赋值参数,并获取结果即可。

 

 

1.1定义DataProvide

定义SqlDataProvider类

  1. /// <summary>  
  2. /// SQL数据提供者的实现  
  3. /// </summary>  
  4. public class SqlDataProvider : DataProviders.IDataProvider  
  5. {  

1.2定义DataProviders.IDataProvider

 

DataProviders.IDataProvider是我定义的一个接口,我希望将来所有的数据提供者都能实现该接口,以便利用依赖倒置实现抽象工厂。

定义DataProviders.IDataProvider接口

  1. public interface IDataProvider  
  2. {  
  3.     void AddParameters(string parname, Guid value);  
  4.     void AddParameters(string parname, long value);  
  5.     void AddParameters(string parname, string value);  
  6.     void AddParameters(string parname, string value, DataProviders.StringFamily dataType);  
  7.     void AddParameters(string parname, string value, DataProviders.StringFamily dataType, int size);  
  8.     void AddParameters(string parname, float value);  
  9.     void AddParameters(string parname, decimal value);  
  10.     void AddParameters(string parname, DateTime value);  
  11.     void AddParameters(string parname, DateTime value, DataProviders.DateFamily dataType);  
  12.     void AddParameters(string parname, int value);  
  13.     void AddParameters(string parname, object value);  
  14.     void AddParameters(string parname, System.Drawing.Bitmap value);  
  15.     void AddParameters(string parname, byte[] value);  
  16.     void AddParameters(string parname, byte[] value, DataProviders.ByteArrayFamily dataType);  
  17.     void AddParameters(string parname, bool value);  
  18.     void AddParameters(string parname, short value);  
  19.     void AddParameters(string parname, byte value);  
  20.     System.Data.CommandType CommandType { get; set; }  
  21.     string ConnectionString { get; }  
  22.     System.Data.DataSet ExecuteDataSet();  
  23.     System.Data.DataTable ExecuteDataTable();  
  24.     void ExecuteReader(ReadData readData);  
  25.     int ExecuteNonQuery();  
  26.     object ExecuteScalar();  
  27.     string SQL { get; set; }  
  28. }  
  29. public delegate void ReadData(System.Data.IDataReader dataReadre); 

从该接口可以看到,实现的DataProvider封装了关于具体的连接对象,命令对象和参数类型的信息。

 

 

1.3实现DataProvider基础部分

SqlDataProvider类实现(基础部分)

  1. private static System.Data.SqlClient.SqlConnection conn;  
  2. private System.Data.SqlClient.SqlCommand cmd;  
  3. /// <summary>  
  4. /// 默认构造函数  
  5. /// </summary>  
  6. public SqlDataProvider()  
  7. {   
  8. }  
  9. /// <summary>  
  10. /// 接受连接字符串  
  11. /// </summary>  
  12. /// <param name="connstr"></param>  
  13. public SqlDataProvider(string connstr)  
  14.     : this(connstr, "")  
  15. {  
  16. }  
  17. /// <summary>  
  18. /// 接受连接字符串和sql字符串  
  19. /// </summary>  
  20. /// <param name="connstr"></param>  
  21. /// <param name="sql"></param>  
  22. public SqlDataProvider(string connstr, string sql)  
  23. {  
  24.     conn = new System.Data.SqlClient.SqlConnection(connstr);  
  25.     cmd = new System.Data.SqlClient.SqlCommand();  
  26.     cmd.Connection = conn;  
  27.     cmd.CommandText = sql;  
  28. }  
  29. /// <summary>  
  30. /// 需要执行的SQL命令  
  31. /// </summary>  
  32. public string SQL  
  33. {  
  34.     set 
  35.     {  
  36.         cmd.CommandText = value;  
  37.     }  
  38.     get  
  39.     {  
  40.         return cmd.CommandText;  
  41.     }  
  42. }  
  43. /// <summary>  
  44. /// 当前的连接字符串  
  45. /// </summary>  
  46. public string ConnectionString  
  47. {  
  48.     get  
  49.     {  
  50.         return conn.ConnectionString;  
  51.     }  
  52. }  
  53. /// <summary>  
  54. /// 设置命令的类型  
  55. /// </summary>  
  56. public System.Data.CommandType CommandType  
  57. {  
  58.     set 
  59.     {  
  60.         cmd.CommandType = value;  
  61.     }  
  62.     get  
  63.     {  
  64.         return cmd.CommandType;  
  65.     }  

从上述代码可以观察到,我们的DataProvider只向用户暴露了两个字符串数据:连接字符串和SQL指令字符串。而将ADO.Net特有的Connection和Command封装起来了。由于用户不需要了解到具体实例化的Connection和Command,则为以后对其他数据源提供者的扩展带来了机会。

 

 

1.4实现DataProvider数据执行部分

SqlDataProvider类实现(数据执行部分)

  1. /// <summary>  
  2. /// 将SqlDataReader提交给具体的委托器处理  
  3. /// </summary>  
  4. /// <param name="readData"></param>  
  5. public void ExecuteReader(ReadData readData)  
  6. {  
  7.     using (conn)  
  8.     {  
  9.         conn.Open();  
  10.         System.Data.SqlClient.SqlDataReader dr = cmd.ExecuteReader();  
  11.         readData(dr);  
  12.         conn.Close();  
  13.     }  
  14. }  
  15. /// <summary>  
  16. /// 对连接执行 Transact-SQL 语句并返回受影响的行数  
  17. /// </summary>  
  18. /// <returns></returns>  
  19. public int ExecuteNonQuery()  
  20. {  
  21.     int result = -1;  
  22.     using (conn)  
  23.     {  
  24.         conn.Open();  
  25.         result = cmd.ExecuteNonQuery();  
  26.         conn.Close();  
  27.     }  
  28.     return result;  
  29. }  
  30. /// <summary>  
  31. /// 执行查询,并返回查询所返回的结果集中第一行的第一列。忽略其他列或行  
  32. /// </summary>  
  33. /// <returns></returns>  
  34. public object ExecuteScalar()  
  35. {  
  36.     object result = null;  
  37.     using (conn)  
  38.     {  
  39.         conn.Open();  
  40.         result = cmd.ExecuteScalar();  
  41.         conn.Close();  
  42.     }  
  43.     return result;  
  44. }  
  45. /// <summary>  
  46. /// 执行查询,并返回查询的DataSet  
  47. /// </summary>  
  48. /// <returns></returns>  
  49. public System.Data.DataSet ExecuteDataSet()  
  50. {  
  51.     System.Data.DataSet datadet = new System.Data.DataSet();  
  52.     using (conn)  
  53.     {  
  54.         System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter();  
  55.         adapter.SelectCommand = cmd;  
  56.         conn.Open();  
  57.         adapter.Fill(datadet);  
  58.         conn.Close();  
  59.     }  
  60.     return datadet;  
  61. }  
  62. /// <summary>  
  63. /// 执行查询,并返回查询的Table 
  64. /// </summary>  
  65. /// <param name="tableIndex"></param>  
  66. /// <returns></returns>  
  67. public System.Data.DataTable ExecuteDataSet(int tableIndex)  
  68. {  
  69.     System.Data.DataSet datadet = ExecuteDataSet();  
  70.     if (datadet.Tables.Count > 0 && tableIndex < datadet.Tables.Count)  
  71.     {  
  72.         return datadet.Tables[tableIndex];  
  73.     }  
  74.     else 
  75.     {  
  76.         return null;  
  77.     }  
  78. }  
  79. /// <summary>  
  80. /// 执行查询,并返回查询的Table 
  81. /// </summary>  
  82. /// <returns></returns>  
  83. public System.Data.DataTable ExecuteDataTable()  
  84. {  
  85.     System.Data.DataTable table = new System.Data.DataTable();  
  86.     using (conn)  
  87.     {  
  88.         System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter();  
  89.         adapter.SelectCommand = cmd;  
  90.         conn.Open();  
  91.         adapter.Fill(table);  
  92.         conn.Close();  
  93.     }  
  94.     return table;  

1.5实现DataProvider参数部分

SqlDataProvider类实现(参数部分)

  1. /// <summary>  
  2. /// 添加一个Variant类型数据  
  3. /// </summary>  
  4. /// <param name="parname"></param>  
  5. /// <param name="value"></param>  
  6. public void AddParameters(string parname, object value)  
  7. {  
  8.     cmd.Parameters.Add(parname, System.Data.SqlDbType.Variant).Value = value;  
  9. }  
  10. /// <summary>  
  11. /// 添加一个Bit类型数据  
  12. /// </summary>  
  13. /// <param name="parname"></param>  
  14. /// <param name="value"></param>  
  15. public void AddParameters(string parname, bool value)  
  16. {  
  17.     cmd.Parameters.Add(parname, System.Data.SqlDbType.Bit).Value = value;  
  18. }  
  19. /// <summary>  
  20. /// 添加一个TinyInt类型数据  
  21. /// </summary>  
  22. /// <param name="parname"></param>  
  23. /// <param name="value"></param>  
  24. public void AddParameters(string parname, byte value)  
  25. {  
  26.     cmd.Parameters.Add(parname, System.Data.SqlDbType.TinyInt).Value = value;  
  27. }  
  28. /// <summary>  
  29. /// 添加一个SmallInt类型数据  
  30. /// </summary>  
  31. /// <param name="parname"></param>  
  32. /// <param name="value"></param>  
  33. public void AddParameters(string parname, short value)  
  34. {  
  35.     cmd.Parameters.Add(parname, System.Data.SqlDbType.SmallInt).Value = value;  
  36. }  
  37. /// <summary>  
  38. /// 添加一个Int类型数据  
  39. /// </summary>  
  40. /// <param name="parname"></param>  
  41. /// <param name="value"></param>  
  42. public void AddParameters(string parname, int value)  
  43. {  
  44.     cmd.Parameters.Add(parname, System.Data.SqlDbType.Int).Value = value;  
  45. }  
  46. /// <summary>  
  47. /// 添加一个BigInt类型数据  
  48. /// </summary>  
  49. /// <param name="parname"></param>  
  50. /// <param name="value"></param>  
  51. public void AddParameters(string parname, long value)  
  52. {  
  53.     cmd.Parameters.Add(parname, System.Data.SqlDbType.BigInt).Value = value;  
  54. }  
  55. /// <summary>  
  56. /// 添加一张图片  
  57. /// </summary>  
  58. /// <param name="parname"></param>  
  59. /// <param name="value"></param>  
  60. public void AddParameters(string parname, System.Drawing.Bitmap value)  
  61. {  
  62.     System.IO.MemoryStream ms = new System.IO.MemoryStream();  
  63.     value.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);  
  64.     AddParameters(parname, ms.ToArray(), ByteArrayFamily.Image);  
  65. }  
  66. /// <summary>  
  67. /// 添加一个Timestamp类型  
  68. /// </summary>  
  69. /// <param name="parname"></param>  
  70. /// <param name="value"></param>  
  71. public void AddParameters(string parname, byte[] value)  
  72. {  
  73.     AddParameters(parname, value, ByteArrayFamily.Timestamp);  
  74. }  
  75. /// <summary>  
  76. /// 添加一个字节数组族类型数据  
  77. /// </summary>  
  78. /// <param name="parname"></param>  
  79. /// <param name="value"></param>  
  80. /// <param name="dateType"></param>  
  81. public void AddParameters(string parname, byte[] value, ByteArrayFamily dataType)  
  82. {  
  83.     cmd.Parameters.Add(parname, DataTypeAdapter.ConvertSqlDbType(dataType)).Value = value;  
  84. }  
  85. /// <summary>  
  86. /// 添加一个字符类型数据,默认是NVarChar,长度是value.Length  
  87. /// </summary>  
  88. /// <param name="parname"></param>  
  89. /// <param name="value"></param>  
  90. public void AddParameters(string parname, string value)  
  91. {  
  92.     AddParameters(parname, value, StringFamily.NVarChar, value.Length);  
  93. }  
  94. /// <summary>  
  95. /// 添加一个字符族类型数据  
  96. /// </summary>  
  97. /// <param name="parname"></param>  
  98. /// <param name="value"></param>  
  99. /// <param name="length"></param>  
  100. public void AddParameters(string parname, string value, int size)  
  101. {  
  102.     AddParameters(parname, value, StringFamily.NVarChar, size);  
  103. }  
  104. /// <summary>  
  105. /// 添加一个字符族类型数据  
  106. /// </summary>  
  107. /// <param name="parname"></param>  
  108. /// <param name="value"></param>  
  109. /// <param name="dateType"></param>  
  110. /// <param name="length"></param>  
  111. public void AddParameters(string parname, string value, StringFamily dataType)  
  112. {  
  113.     AddParameters(parname, value,dataType, value.Length);  
  114. }  
  115. /// <summary>  
  116. /// 添加一个字符族类型数据  
  117. /// </summary>  
  118. /// <param name="parname"></param>  
  119. /// <param name="value"></param>  
  120. /// <param name="dateType"></param>  
  121. /// <param name="size"></param>  
  122. public void AddParameters(string parname, string value, StringFamily dataType, int size)  
  123. {  
  124.     cmd.Parameters.Add(parname, DataTypeAdapter.ConvertSqlDbType(dataType), size).Value = value;  
  125. }  
  126. /// <summary>  
  127. /// 添加一个SmallDateTime类型数据  
  128. /// </summary>  
  129. /// <param name="parname"></param>  
  130. /// <param name="value"></param>  
  131. public void AddParameters(string parname, DateTime value)  
  132. {  
  133.     AddParameters(parname, value, DateFamily.SmallDateTime);  
  134. }  
  135. /// <summary>  
  136. /// 添加一个日期族类型数据  
  137. /// </summary>  
  138. /// <param name="parname"></param>  
  139. /// <param name="value"></param>  
  140. /// <param name="dateType"></param>  
  141. public void AddParameters(string parname, DateTime value, DateFamily dataType)  
  142. {  
  143.     cmd.Parameters.Add(parname, DataTypeAdapter.ConvertSqlDbType(dataType)).Value = value;  
  144. }  
  145. /// <summary>  
  146. /// 添加一个Decimal类型数据  
  147. /// </summary>  
  148. /// <param name="parname"></param>  
  149. /// <param name="value"></param>  
  150. public void AddParameters(string parname, decimal value)  
  151. {  
  152.     cmd.Parameters.Add(parname, System.Data.SqlDbType.Decimal).Value = value;  
  153. }  
  154. /// <summary>  
  155. /// 添加Float类型数据  
  156. /// </summary>  
  157. /// <param name="parname"></param>  
  158. /// <param name="value"></param>  
  159. public void AddParameters(string parname, float value)  
  160. {  
  161.     cmd.Parameters.Add(parname, System.Data.SqlDbType.Float).Value = value;  
  162. }  
  163. /// <summary>  
  164. /// 添加一个UniqueIdentifier类型数据  
  165. /// </summary>  
  166. /// <param name="parname"></param>  
  167. /// <param name="value"></param>  
  168. public void AddParameters(string parname, System.Guid value)  
  169. {  
  170.     cmd.Parameters.Add(parname, System.Data.SqlDbType.UniqueIdentifier).Value = value;  

ADO.Net对参数的处理冗长的很,需要很多代码,我们的DataProvider通过重载简单的实现了对参数的处理,使用者在大多数情况下只需要提供两个参数:参数的名称和值。DataProvider和根据值的类型推算应该使用具体的哪个System.Data.SqlDbType。

 

1.6定义C#和SQL数据类型的关系

 

不过,另人烦恼的是C#的数据类型和SQL的数据类型不是简单的一对一的关系,而是一对多的复杂关系,我们需要有一个方法来适配数据类型的不同。

数据类型的关系定义

  1. /// <summary>  
  2. /// C#对于的SQL类型  
  3. /// </summary>  
  4. public enum StringFamily  
  5. {  
  6.     Char,  
  7.     NChar,  
  8.     NText,  
  9.     NVarChar,  
  10.     Text,  
  11.     VarChar      
  12. }  
  13. /// <summary>  
  14. /// C#对于的SQL类型  
  15. /// </summary>  
  16. public enum DateFamily  
  17. {  
  18.     DateTime,  
  19.     SmallDateTime,  
  20.     Date,  
  21.     Time,  
  22.     DateTime2,  
  23.     DateTimeOffset      
  24. }  
  25. /// <summary>  
  26. /// C#对于的SQL类型  
  27. /// </summary>  
  28. public enum ByteArrayFamily  
  29. {  
  30.     Binary,  
  31.     Image,  
  32.     Timestamp,  
  33.     VarBinary  

1.7定义数据类型的适配器

DataTypeAdapter的定义

  1. /// <summary>  
  2. /// SqlDbType数据类型和.NET Framework数据类型的适配器  
  3. /// </summary>  
  4. public static class DataTypeAdapter  
  5. {  
  6.     /// <summary>  
  7.     /// 将.NET Framework数据类型适配为SqlDbType数据类型  
  8.     /// </summary>  
  9.     /// <param name="data"></param>  
  10.     /// <returns></returns>  
  11.     public static System.Data.SqlDbType ConvertSqlDbType(StringFamily data)  
  12.     {  
  13.         switch (data)  
  14.         {  
  15.             case StringFamily.Char:  
  16.                 return System.Data.SqlDbType.Char;  
  17.             case StringFamily.NChar:  
  18.                 return System.Data.SqlDbType.NChar;  
  19.             case StringFamily.NText:  
  20.                 return System.Data.SqlDbType.NText;  
  21.             case StringFamily.NVarChar:  
  22.                 return System.Data.SqlDbType.NVarChar;  
  23.             case StringFamily.Text:  
  24.                 return System.Data.SqlDbType.Text;  
  25.             default:  
  26.                 return System.Data.SqlDbType.VarChar;  
  27.         }  
  28.     }  
  29.     /// <summary>  
  30.     /// 将.NET Framework数据类型适配为SqlDbType数据类型  
  31.     /// </summary>  
  32.     /// <param name="data"></param>  
  33.     /// <returns></returns>  
  34.     public static System.Data.SqlDbType ConvertSqlDbType(DateFamily data)  
  35.     {  
  36.         switch (data)  
  37.         {  
  38.             case DateFamily.Date:  
  39.                 return System.Data.SqlDbType.Date;  
  40.             case DateFamily.DateTime:  
  41.                 return System.Data.SqlDbType.DateTime;  
  42.             case DateFamily.DateTime2:  
  43.                 return System.Data.SqlDbType.DateTime2;  
  44.             case DateFamily.DateTimeOffset:  
  45.                 return System.Data.SqlDbType.DateTimeOffset;  
  46.             case DateFamily.SmallDateTime:  
  47.                 return System.Data.SqlDbType.SmallDateTime;  
  48.             default:  
  49.                 return System.Data.SqlDbType.Time;  
  50.         }  
  51.     }  
  52.     /// <summary>  
  53.     /// 将.NET Framework数据类型适配为SqlDbType数据类型  
  54.     /// </summary>  
  55.     /// <param name="data"></param>  
  56.     /// <returns></returns>  
  57.     public static System.Data.SqlDbType ConvertSqlDbType(ByteArrayFamily data)  
  58.     {  
  59.         switch (data)  
  60.         {  
  61.             case ByteArrayFamily.Binary:  
  62.                 return System.Data.SqlDbType.Binary;  
  63.             case ByteArrayFamily.Image:  
  64.                 return System.Data.SqlDbType.Image;  
  65.             case ByteArrayFamily.Timestamp:  
  66.                 return System.Data.SqlDbType.Timestamp;  
  67.             default:  
  68.                 return System.Data.SqlDbType.VarBinary;  
  69.         }  
  70.     }  

通过上述的数据类型适配,我们将使用者和ADO.Net直接的具体关系弱耦合了。

 

 

1.8使用DataProvider

使用DataProvider(Select)

  1. DataProviders.IDataProvider provider = CreateDataProvider();  
  2. provider.SQL = "SELECT CompanyID as [Identity],Name,ShortName,Code,LegalEntity,Address,PostalCode,Type as CompanyType,CityID,Version " +  
  3.                 "FROM lt_dictionary.Company WHERE CityID=@cityid";  
  4. provider.AddParameters("@cityid", cityID);  
  5. return provider.ExecuteDataTable(); 

使用DataProvider(Update)

  1. DataProviders.IDataProvider provider = CreateDataProvider();  
  2. provider.SQL = "UPDATE lt_dictionary.Company " +  
  3.                 "SET " +  
  4.                 "Name=@name, " +  
  5.                 "ShortName=@shortName," +  
  6.                 "Code=@code," +  
  7.                 "LegalEntity=@legalEntity," +  
  8.                 "Address=@address," +  
  9.                 "PostalCode=@postalCode, " +  
  10.                 "Type=@type," +  
  11.                 "CityID=@cityID " +  
  12.                 "WHERE CompanyID=@id AND Version=@ver";  
  13. provider.AddParameters("@name", company.Name);  
  14. provider.AddParameters("@shortName", company.ShortName);  
  15. provider.AddParameters("@Code", company.Code);  
  16. provider.AddParameters("@LegalEntity", company.LegalEntity);  
  17. provider.AddParameters("@address", company.Address);  
  18. provider.AddParameters("@postalCode", company.PostalCode);  
  19. provider.AddParameters("@type", company.CompanyType.ToString());  
  20. provider.AddParameters("@cityID", company.City.Identity);  
  21. provider.AddParameters("@id", original_company.Identity);  
  22. provider.AddParameters("@ver", original_company.Version, DataProviders.ByteArrayFamily.Timestamp);  
  23. return provider.ExecuteNonQuery() > 0; 

使用DataProvider(Insert)

  1. DataProviders.IDataProvider provider = CreateDataProvider();  
  2. provider.SQL = "INSERT INTO lt_dictionary.City " +  
  3.                 "([Name],PostalCode,DistanceCode,Province,Longitude,Latitude)" +  
  4.                 "VALUES " +  
  5.                 "(@Name,@PostalCode,@DistanceCode,@Province,@Longitude,@Latitude)";  
  6. provider.AddParameters("@name", city.Name);  
  7. provider.AddParameters("@postalCode", city.PostalCode);  
  8. provider.AddParameters("@distanceCode", city.DistanceCode);  
  9. provider.AddParameters("@province", city.Province);  
  10. provider.AddParameters("@longitude", city.Longitude);  
  11. provider.AddParameters("@latitude", city.Latitude);  
  12. return provider.ExecuteNonQuery() > 0; 

通过上述的代码,可以发现,使用了我们的DataProvider后,程序员对ADO.Net的了解被降到最低程度,其只要关心具体的SQL指令和参数的赋值,其他内容不再需要其关注。很高程度的提高了程序员的开发效率。

 

 

二、打造实体基类

 

关系型数据表中一般有共性的部分是所有的实体都有ID(但ID的类型不一样),很多业务表都有主从的关系。

 

 

2.1表定义

 

比如下面的表

City定义

  1. CREATE TABLE [lt_dictionary].[City](  
  2.     [CityID] [int] IDENTITY(1,1) NOT NULL,  
  3.     [Name] [nvarchar](50) NOT NULL,  
  4.     [PostalCode] [dbo].[PostalCodeType] NOT NULL,  
  5.     [DistanceCode] [nvarchar](5) NOT NULL,  
  6.     [Province] [nvarchar](3) NOT NULL,  
  7.     [Longitude] [decimal](5, 2) NOT NULL,  
  8.     [Latitude] [decimal](5, 2) NOT NULL,  
  9.     [Enable] [dbo].[EnableType] NOT NULL CONSTRAINT [DF_City_Enable] DEFAULT ((1)),  
  10.     [LastEditDate] [dbo].[BusinessDateType] NOT NULL CONSTRAINT [DF_City_LastEditDate] DEFAULT (getdate()),  
  11.     [UpdateDay] AS (datediff(day,[LastEditDate],getdate())),  
  12.     [Version] [timestamp] NOT NULL,  
  13.  CONSTRAINT [PK_City] PRIMARY KEY CLUSTERED   
  14. (  
  15.     [CityID] ASC 

这个城市表的ID是int的。

BusinessOrders定义

  1. CREATE TABLE [lt_business].[BusinessOrders](  
  2.     [BusinessOrderID] [uniqueidentifier] NOT NULL CONSTRAINT [DF_BusinessOrders_BusinessOrderID] DEFAULT (newid()),  
  3.     [Number] [dbo].[BusinessOrderType] NOT NULL CONSTRAINT [DF_BusinessOrders_Number] DEFAULT ([dbo].[CreateBusinessOrderNumber]('Bz')),  
  4.     [Deadline] [dbo].[BusinessDateType] NOT NULL,  
  5.     [PaymentMethod] [nchar](2) NOT NULL,  
  6.     [PaymentEnterprise] [dbo].[DescriptionType] NOT NULL,  
  7.     [Origin] [dbo].[DescriptionType] NOT NULL,  
  8.     [Destination] [dbo].[DescriptionType] NOT NULL,  
  9.     [DeliveryType] [nchar](2) NOT NULL,  
  10.     [Level] [dbo].[LevelType] NOT NULL,  
  11.     [Remark] [dbo].[DescriptionType] NOT NULL,  
  12.     [Indicator] [nvarchar](3) NOT NULL,  
  13.     [FreightPayable] [dbo].[DescriptionType] NOT NULL,  
  14.     [WarehouseID] [int] NOT NULL,  
  15.     [OrderID] [uniqueidentifier] NOT NULL,  
  16.     [BusinessDate] [dbo].[BusinessDateType] NOT NULL CONSTRAINT [DF_BusinessOrders_BusinessDate] DEFAULT (getdate()),  
  17.     [StaffID] [int] NOT NULL,  
  18.     [Version] [timestamp] NOT NULL,  
  19.     [State] AS ([dbo].[GetBusinessOrderState]([BusinessOrderID])),  
  20.  CONSTRAINT [PK_BusinessOrders] PRIMARY KEY CLUSTERED  

BusinessOrders的ID是uniqueidentifier类型。

BusinessOrderDetaileds定义

  1. CREATE TABLE [lt_business].[BusinessOrderDetaileds](  
  2.     [BusinessOrderDetailedID] [uniqueidentifier] NOT NULL CONSTRAINT [DF_BusinessOrderDetaileds_BusinessOrderDetailedID] DEFAULT (newid()),  
  3.     [BusinessOrderID] [uniqueidentifier] NOT NULL,  
  4.     [Serial] [int] NOT NULL,  
  5.     [GoodsDescription] [dbo].[DescriptionType] NOT NULL,  
  6.     [Packing] [nvarchar](2) NOT NULL,  
  7.     [Quantity] [decimal](18, 2) NOT NULL,  
  8.     [TotalPackages] [decimal](18, 2) NOT NULL,  
  9.     [Weight] [decimal](18, 2) NOT NULL,  
  10.     [Measurement] [decimal](18, 2) NOT NULL,  
  11.     [Version] [timestamp] NOT NULL,  
  12.     [State] AS ([dbo].[GetBusinessOrderItmeState]([BusinessOrderDetailedID])),  
  13.     [CompleteQuantity] AS ([dbo].[GetBusinessOrderItmeCompleteQuantity]([BusinessOrderDetailedID])),  
  14.  CONSTRAINT [PK_BusinessOrderDetaileds] PRIMARY KEY CLUSTERED  

BusinessOrderDetaileds的ID是uniqueidentifier类型,其外键对应的是BusinessOrders实体的ID。

 

 

2.2关于实体基类定义的要求

 

我希望有这个的实体基类,该实体定义了所有的继承者(实体的具体实现类)都必须有ID属性,但ID属性的数据类型由各实体自己定义。我还希望,能在类的定义上看出有主从表的关系,并且能约束主从表的一些行为。而且我还希望基类能自动的实现对属性的赋值。

 

 

2.3定义EntityBase

EntityBase定义(继承部分)

  1. [Serializable]  
  2. public abstract class EntityBase<T,ID> where T : EntityBase<T,ID>  
  3. {  
  4.     /// <summary>  
  5.     /// 所有的实体都必须有一个唯一标识,具体类型有实体各自实现  
  6.     /// </summary>  
  7.     [System.ComponentModel.DataObjectField(true, true, false)]  
  8.     public virtual ID Identity  
  9.     {  
  10.         set;  
  11.         get;  
  12.     }  

EntityBase定义了一个ID的泛型,该泛型描述了继承者必须实现具体的ID类型。

 

 

2.4定义EntityBase的Undo功能

 

在没有泛型的年代时,基类无了解子类的类型,因此基类只能实现一些返回或参数是基本数据类型的方法,如果要为子类提供个性化的方法,基类只能以object对象返回,且要求子类实现数据类型的强制转换。但现在,EntityBase还提供了一个T类型,因此我们可以实现Undo的功能。

EntityBase(Undo部分)

  1. /// <summary>  
  2. /// 实体是否支持撤销  
  3. /// </summary>  
  4. public abstract bool HasUndo  
  5. {  
  6.     get;  
  7. }  
  8. /// <summary>  
  9. /// 还可以撤销的次数  
  10. /// </summary>  
  11. public int UndoCount  
  12. {  
  13.     get  
  14.     {  
  15.         return undoStack.Count;  
  16.     }  
  17. }  
  18. /// <summary>  
  19. /// 得到实体的副本  
  20. /// </summary>  
  21. /// <returns></returns>  
  22. protected virtual T Clone()  
  23. {  
  24.     return (T)this.MemberwiseClone();  
  25. }  
  26. /// <summary>  
  27. /// 将复本入栈  
  28. /// </summary>  
  29. protected void Push()  
  30. {  
  31.     if (this.HasUndo)  
  32.     {  
  33.         this.Push((T)this.Clone());  
  34.     }  
  35. }  
  36. /// <summary>  
  37. /// 将复本入栈  
  38. /// </summary>  
  39. /// <param name="obj"></param>  
  40. private void Push(T obj)  
  41. {  
  42.     if (this.HasUndo)  
  43.     {  
  44.         undoStack.Push(obj.Clone());  
  45.     }  
  46. }  
  47. private System.Collections.Generic.Stack<T> undoStack = new Stack<T>();  
  48. /// <summary>  
  49. /// 将复本出栈  
  50. /// </summary>  
  51. /// <returns></returns>  
  52. private T Pop()  
  53. {  
  54.     if (undoStack.Count > 0)  
  55.     {  
  56.         return undoStack.Pop();  
  57.     }  
  58.     else 
  59.     {  
  60.         return null;  
  61.     }  
  62. }  
  63. /// <summary>  
  64. /// 撤销  
  65. /// </summary>  
  66. /// <returns></returns>  
  67. public T Undo()  
  68. {  
  69.     return Pop();  

使用了泛型,我们在类的内部提供了泛型队列,然后返回值和参数值都是泛型T,该T将由各个子类来具体实现。

 

 

2.5定义EntityBase的数据访问能力

  1. /// <summary>  
  2. /// 根据给定的连接字符串构造数据提供者  
  3. /// </summary>  
  4. /// <param name="connStr"></param>  
  5. /// <returns></returns>  
  6. protected static DataProviders.IDataProvider CreateDataProvider(string connStr)  
  7. {  
  8.     return new DataProviders.SqlDataProvider.SqlDataProvider(connStr);  

2.6定义EntityBase的构造函数

 

EntityBase有一个接受System.Data.DataTable的构造函数,该构造函数将table中指定行的数据和本类的属性作对比,如果名称和数据类型匹配,则自动赋值。

EntityBase构造函数

  1. /// <summary>  
  2. /// 按table的指定行数据进行属性的初始化  
  3. /// </summary>  
  4. /// <param name="table"></param>  
  5. /// <param name="indexRow"></param>  
  6. public EntityBase(System.Data.DataTable table, int indexRow)  
  7. {  
  8.     //遍历table中的每一列  
  9.     for (int i = 0; i <= table.Columns.Count - 1; i++)  
  10.     {  
  11.         //按列的名称,试图从当前对象中获取同名属性  
  12.         System.Reflection.PropertyInfo pinfo = this.GetType().GetProperty(table.Columns[i].ColumnName);  
  13.         if (pinfo != null)  
  14.         {//如果存在该属性  
  15.             object value = table.Rows[indexRow][table.Columns[i].ColumnName];//提取列的当前行值  
  16.             if (pinfo.PropertyType == table.Columns[i].DataType)//如果对象属性定义的类型和table的列的类型一致  
  17.             {  
  18.                 pinfo.SetValue(this, value, null);//赋值  
  19.             }  
  20.             else 
  21.             {  
  22.                 if (pinfo.PropertyType.IsEnum)//如果对象属性的值是枚举类型  
  23.                 {  
  24.                     if (value.GetType() == typeof(int))//数据库中保存的是int类型,则直接为枚举赋值  
  25.                     {  
  26.                         pinfo.SetValue(this, value, null);//赋值  
  27.                     }  
  28.                     if (value.GetType() == typeof(string))//如果数据库中保存的是string类型  
  29.                     {  
  30.                         pinfo.SetValue(this, Enum.Parse(pinfo.PropertyType, value.ToString(), false), null);//赋值  
  31.                     }  
  32.                 }  
  33.                 //如果对象的属性是Bitmap类型,对应的数据值是byte[]  
  34.                 if (pinfo.PropertyType==typeof(System.Drawing.Bitmap) && value.GetType()==typeof(byte[]))  
  35.                 {  
  36.                     pinfo.SetValue(this, new System.Drawing.Bitmap(new System.IO.MemoryStream((byte[])value)), null);//赋值  
  37.                 }  
  38.             }  
  39.         }  
  40.     }  

2.7定义EntityBase的CreateInstance

 

虽然EntityBase的构造函数有能力实现对属性的自动赋值,但我们可能要实例对象的集合或决定table中是否有值,应此我们需要实现CreateInstance方法。

定义EntityBase的CreateInstances方法

  1. /// <summary>  
  2. /// 通过table实例化一组对象  
  3. /// </summary>  
  4. /// <param name="table"></param>  
  5. /// <returns></returns>  
  6. public static List<T> CreateInstances(System.Data.DataTable table, int startRecord, int maxRecords)  
  7. {  
  8.     List<T> instances = new List<T>();  
  9.     for (int i = startRecord; i <= maxRecords; i++)  
  10.     {  
  11.         instances.Add(CreateInstance(table, i));  
  12.     }  
  13.     return instances;  
  14. }
  15. /// <summary>  
  16. /// 通过table实例化一个对象  
  17. /// </summary>  
  18. /// <param name="table"></param>  
  19. /// <param name="startRecord"></param>  
  20. /// <param name="maxRecords"></param>  
  21. /// <returns></returns>  
  22. public static T CreateInstance(System.Data.DataTable table, int rowIndex)  
  23. {  
  24.     if (table.Rows.Count > rowIndex)  
  25.     {  
  26.         return (T)System.Activator.CreateInstance(typeof(T), table, rowIndex);  
  27.     }  
  28.     else 
  29.     {  
  30.         return null;  
  31.     }  
  32. }  
  33. /// <summary>  
  34. /// 默认按table的第一行实例化一个对象  
  35. /// </summary>  
  36. /// <param name="table"></param>  
  37. /// <returns></returns>  
  38. public static T CreateInstance(System.Data.DataTable table)  
  39. {  
  40.     return CreateInstance(table, 0);  
  41. }  
  42. /// <summary>  
  43. /// 通过table实例化一组对象  
  44. /// </summary>  
  45. /// <param name="table"></param>  
  46. /// <param name="startRecord"></param>  
  47. /// <returns></returns>  
  48. public static List<T> CreateInstances(System.Data.DataTable table, int startRecord)  
  49. {  
  50.     return CreateInstances(table, startRecord, table.Rows.Count - 1);  
  51. }  
  52. /// <summary>  
  53. /// 通过table实例化一组对象  
  54. /// </summary>  
  55. /// <param name="table"></param>  
  56. /// <returns></returns>  
  57. public static List<T> CreateInstances(System.Data.DataTable table)  
  58. {  
  59.     return CreateInstances(table, 0, table.Rows.Count - 1);  
  60. }  

2.8使用EntityBase

返回单个实例对象

  1. /// <summary>  
  2. /// 按指定的名字返回城市对象  
  3. /// </summary>  
  4. /// <param name="cityName"></param>  
  5. /// <returns></returns>  
  6. [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select, false)]  
  7. public static City SelectByName(string cityName)  
  8. {  
  9.     DataProviders.IDataProvider provider = CreateDataProvider();  
  10.     provider.SQL = "SELECT CityID as [Identity],Name,PostalCode,DistanceCode,Province,Longitude,Latitude,Version " +  
  11.                     "FROM lt_dictionary.City WHERE Name=@name";  
  12.     provider.AddParameters("@name", cityName);  
  13.     return CreateInstance(provider.ExecuteDataTable());  

返回集合对象

  1. /// <summary>  
  2. /// 返回所有禁用状态的城市信息  
  3. /// </summary>  
  4. /// <returns></returns>  
  5. [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select, false)]  
  6. public static List<City> SelectIsDisabled()  
  7. {  
  8.     DataProviders.IDataProvider provider = CreateDataProvider();  
  9.     provider.SQL = "SELECT CityID as [Identity],Name,PostalCode,DistanceCode,Province,Longitude,Latitude,Version " +  
  10.                     "FROM lt_dictionary.City WHERE Enable=0";  
  11.     //返回List<City>  
  12.     return CreateInstances(provider.ExecuteDataTable());  

三、定义主从实体基类

 

定义主从实体基类的原因是我希望在类的定义时,可以很明确的了解类之间的主从关系。

 

 

 

3.1定义主表基类

定义PrimaryDataEntityBase

  1. /// <summary>  
  2. /// 描述主从表的主表的数据实体  
  3. /// </summary>  
  4. /// <typeparam name="ID">主表实体的主键ID</typeparam>  
  5. /// <typeparam name="P">主表</typeparam>  
  6. /// <typeparam name="F">从表</typeparam>  
  7. public abstract lass PrimaryDataEntityBase<ID, P, F> : EntityBase<P, ID>  
  8.     where P : PrimaryDataEntityBase<ID, P, F>  
  9.     where F : ForeignDataEntityBase<ID, P, F>  
  10. {  
  11.     /// <summary>  
  12.     /// PrimaryDataEntityBase的默认构造函数  
  13.     /// </summary>  
  14.     public PrimaryDataEntityBase()  
  15.     {  
  16.     }  
  17.     /// <summary>  
  18.     /// 按table的第一行数据进行属性的初始化  
  19.     /// </summary>  
  20.     /// <param name="table"></param>  
  21.     public PrimaryDataEntityBase(System.Data.DataTable table)  
  22.         : this(table, 0)  
  23.     {  
  24.     }  
  25.     /// <summary>  
  26.     /// 按table的指定行数据进行属性的初始化  
  27.     /// </summary>  
  28.     /// <param name="table"></param>  
  29.     /// <param name="indexRow"></param>  
  30.     public PrimaryDataEntityBase(System.Data.DataTable table, int indexRow)  
  31.         : base(table, indexRow)  
  32.     {  
  33.     }  
  34.     /// <summary>  
  35.     /// 装载当前从表的详细项  
  36.     /// </summary>  
  37.     protected abstract List<F> LoadDetailedItems();  
  38.     /// <summary>  
  39.     /// 存放外键表的数据项目的集合  
  40.     /// </summary>  
  41.     protected List<F> items = new List<F>();  
  42.     /// <summary>  
  43.     /// 获取外键表数据的集合  
  44.     /// </summary>  
  45.     public List<F> DetailedItems  
  46.     {  
  47.         get  
  48.         {  
  49.             return LoadDetailedItems();  
  50.         }  
  51.     }  
  52.     /// <summary>  
  53.     /// 返回外键表的数据项目数量  
  54.     /// </summary>  
  55.     public int DetailedItemCount  
  56.     {  
  57.         get  
  58.         {  
  59.             return items.Count;  
  60.         }  
  61.     }  
  62.     /// <summary>  
  63.     /// 将一个外键实体加入集合  
  64.     /// </summary>  
  65.     /// <param name="item"></param>  
  66.     /// <returns></returns>  
  67.     public abstract void Add(F item);  
  68.     /// <summary>  
  69.     /// 从集合中移除一个外键实体  
  70.     /// </summary>  
  71.     /// <param name="item"></param>  
  72.     public abstract void Remove(F item);  
  73.     /// <summary>  
  74.     /// 从集合中移除一个外键实体  
  75.     /// </summary>  
  76.     /// <param name="index"></param>  
  77.     public abstract void RemoveAt(int index);  
  78.     /// <summary>  
  79.     /// 返回或设置匹配索引的订单详细项  
  80.     /// </summary>  
  81.     /// <param name="index"></param>  
  82.     /// <returns></returns>  
  83.     public abstract F this[int index]  
  84.     {  
  85.         set;  
  86.         get;  
  87.     }  

3.2定义从表基类

定义ForeignDataEntityBase

  1. /// <summary>  
  2. /// 描述主从表的从表的数据实体  
  3. /// </summary>  
  4. /// <typeparam name="ID">从表实体的主键ID</typeparam>  
  5. /// <typeparam name="P">主表</typeparam>  
  6. /// <typeparam name="F">从表</typeparam>  
  7. public abstract class ForeignDataEntityBase<ID, P, F> : EntityBase<F, ID>  
  8.     where P : PrimaryDataEntityBase<ID, P, F>  
  9.     where F : ForeignDataEntityBase<ID, P, F>  
  10. {  
  11.     /// <summary>  
  12.     /// ForeignDataEntityBase的默认构造函数  
  13.     /// </summary>  
  14.     public ForeignDataEntityBase()  
  15.     {  
  16.     }  
  17.     /// <summary>  
  18.     /// 按table的第一行数据进行属性的初始化  
  19.     /// </summary>  
  20.     /// <param name="table"></param>  
  21.     public ForeignDataEntityBase(System.Data.DataTable table)  
  22.         : this(table, 0)  
  23.     {  
  24.     }  
  25.     /// <summary>  
  26.     /// 按table的指定行数据进行属性的初始化  
  27.     /// </summary>  
  28.     /// <param name="table"></param>  
  29.     /// <param name="indexRow"></param>  
  30.     public ForeignDataEntityBase(System.Data.DataTable table, int indexRow)  
  31.         : base(table, indexRow)  
  32.     {  
  33.     }  
  34.     /// <summary>  
  35.     /// 对应主键实体  
  36.     /// </summary>  
  37.     [System.ComponentModel.DataObjectField(false, false, false)]  
  38.     public P RelationOrder  
  39.     {  
  40.         set;  
  41.         get;  
  42.     }  

3.3使用主从表基类

  1. /// <summary>  
  2. /// 客户委托单  
  3. /// </summary>  
  4. [System.ComponentModel.DataObject(true)]  
  5. public class BusinessOrder : LogisticsOrderBase<BusinessOrder, BusinessOrderItem>  
  6. {  
  7. }  
  8. /// <summary>  
  9. /// 委托单详细  
  10. /// </summary>  
  11. [System.ComponentModel.DataObject(true)]  
  12. public class BusinessOrderItem : DetailedItemBase<BusinessOrder, BusinessOrderItem>  
  13. {  

现在我们的类在定义的时候,就可以非常明确的描述了主从实体的关系,并拥有了数据自动属性装载的能力。

 

 

 

 

四、结论

 

反射可以让我们动态的了解对象的所有成员,通过对tabel的遍历和对本对象的遍历,我们可以很方便的实现对属性的赋值,减少了程序员的重复性工作。而减少代码并不是可以偷懒,而是可以降低错误的产生机会。

而泛型可以在具体的实现之前,定义将来的类型,并且能对类型做出很多约束。使用了泛型以后,我们可以在基类为子类的多态做出更多的多态实现,现在我们的类对于静态方法也可以产生多态了,不是吗?