任何系統的基礎,都可以算是各種數據的增刪改查(CRUD)。最早操作數據是直接在代碼里寫SQL語句,后來出現了各種ORM框架。C#下的ORM框架有很多,如微軟自己的Entity Framework、第三方的NHibernate。這些ORM框架甚至可以直接隱去具體SQL語句,讓開發人員直接面向持久化后的對象。
雖然這種ORM框架大大簡化了開發流程,但也帶來了一個很大的問題。開發人員不了解SQL,而有時候這些ORM框架自己生成的SQL語句效率極低。
想象一個場景:
某天DBA說:”那誰誰誰,這個數據操作性能太低,得優化一下。”
開發人員:“這是NHibernate自動生成的,關我屁事”
DBA:“這個查詢跨表太多,容易造成死鎖,需要修改一下”
開發人員:“這是NHibernate自動生成的SQL,我不會修改啊”
DBA:“%¥…*&(*“
所以在大型項目里,ORM封裝的太好,反而有時候會帶來問題。說句題外話,微軟太照顧開發人員了,各種封裝,雖然用起來舒服,開發效率快,但是對於一些特殊場景性能就跟不上。有一次一個團隊成員用ASP.net的Treeview控件做樹形菜單,生成的Html源代碼足足有20M。
我自己用的ORM框架是多層+代碼生成器。多層提供數據持久化,代碼生成器生成基本的CRUD操作,如果有更復雜的業務,可以擴充。這樣既可以封裝重復的底層操作,讓開發人員集中精力到業務上,也可以提供足夠的靈活性。
代碼生成器是基於動軟代碼生成器(http://www.maticsoft.com/codematic.aspx)2.41版本的源碼做的二次開發:
在開發過程中,數據表發生變化,很常見。這樣每次都要重新生成三層代碼。為了防止新生成的代碼覆蓋我擴充的方法,可以將使用分部類將擴充方法放到單獨一個文件中。舉例來說:這有個用戶類Users.cs,里面放了有關用戶的CRUD:
我有個擴充方法Login,就可以新建一個Users的分部類,名字命名為Users.designer.cs 。VS會自動將這個文件折疊到User.cs的里面:
這樣我以后即使重新生成代碼,也不會覆蓋掉擴充方法。
不過我覺得仍然過於繁瑣。開發階段表字段變來變去,每次都要重新覆蓋3個文件,老陷入這種重復性的工作,真是有損程序員的清名。仔細考慮下,數據表字段和Model屬性是一致的,而CRUD基本上都是對Model的操作。這樣依靠反射和泛型兩尊大神,可以將SQLHelper做進一步的簡化:
- 新增和修改
Dataset構造一個DataRow,把Model中和表字段對應屬性的值復制過來,然后提交到數據庫。代碼如下(同時支持新增和修改。如果數據庫存在該記錄,為修改,不存在為新增):
public static bool ModelToDatabase(object model, string tabname) { Type ObjType = model.GetType(); PropertyInfo[] ObjProInfos = ObjType.GetProperties(); string sql = ""; DataSet ds = null; DataTable dt = null; DataRow dr = null; //檢測是新增記錄還是修改記錄 SqlConnection conn = Conn(); try { SqlDataAdapter oda = null; bool IsEditFlag = false; //-------------------------- foreach (PropertyInfo ObjProInfo in ObjProInfos) { if (ObjProInfo.Name.ToLower() == "id") { object IdObject = ObjProInfo.GetValue(model, null); if (IdObject != null) { sql = "select top 1 * from " + tabname + " where id=" + StringPlus.SafeStrN(IdObject.ToString()); oda = new SqlDataAdapter(sql, conn); ds = new DataSet(); oda.Fill(ds, tabname); conn.Close(); dt = ds.Tables[0]; if (dt.Rows.Count == 0) { dr = dt.NewRow(); IsEditFlag = false; } else { dr = dt.Rows[0]; IsEditFlag = true; } break; } else { sql = "select top 1 * from " + tabname; oda = new SqlDataAdapter(sql, conn); ds = new DataSet(); oda.Fill(ds, tabname); conn.Close(); dt = ds.Tables[0]; dr = dt.NewRow(); IsEditFlag = false; break; } } } //如果不存在ID這一個項,視為新建 if (!IsEditFlag) { sql = "select top 1 * from " + tabname; oda = new SqlDataAdapter(sql, conn); ds = new DataSet(); oda.Fill(ds, tabname); conn.Close(); dt = ds.Tables[0]; dr = dt.NewRow(); } if (conn.State != ConnectionState.Closed) conn.Close(); //開始構築一條datarow ArrayList col = new ArrayList(dt.Columns.Count); for (int j = 0; j < dt.Columns.Count; j++) { col.Add(dt.Columns[j].ColumnName.ToLower()); } foreach (PropertyInfo ObjProInfo in ObjProInfos) { foreach (string DataColumn in col) { object objValue = ObjProInfo.GetValue(model, null); if (DataColumn.ToLower() == ObjProInfo.Name.ToLower()) { if (objValue != null) { dr[ObjProInfo.Name] = objValue; } else { dr[ObjProInfo.Name] = DBNull.Value; } break; } } } //將更改或新增的記錄更新到數據庫 if (!IsEditFlag) { dt.Rows.Add(dr); SqlCommandBuilder ocb = new SqlCommandBuilder(oda); ocb.ConflictOption = ConflictOption.OverwriteChanges; ocb.QuotePrefix = "["; ocb.QuoteSuffix = "]"; SqlCommand odc = ocb.GetInsertCommand(); oda.Update(dt); } else { SqlCommandBuilder ocb = new SqlCommandBuilder(oda); ocb.ConflictOption = ConflictOption.OverwriteChanges; ocb.QuotePrefix = "["; ocb.QuoteSuffix = "]"; SqlCommand odc = ocb.GetInsertCommand(); oda.Update(dt); }} </span><span style="color: #0000ff">catch</span><span style="color: #000000"> (Exception) { } </span><span style="color: #0000ff">finally</span><span style="color: #000000"> { </span><span style="color: #0000ff">if</span> (conn.State !=<span style="color: #000000"> ConnectionState.Closed) { conn.Close(); } } </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">true</span><span style="color: #000000">;}
- 獲取一個Model
這個是上一個操作的逆操作。先獲取DataRow,然后把泛型實體化,通過反射賦值后返回:
/// <summary> /// 通過一個Sql查詢語句返回一個Model /// </summary> /// <typeparam name="T"></typeparam> /// <param name="sql"></param> /// <returns></returns> public static T DatabaseToModel<T>(string sql) where T : new() { T model = new T(); PropertyInfo[] MyProInfo = model.GetType().GetProperties(); DataSet MyDS = ReturnDataset(sql); DataTable MyDT = MyDS.Tables[0]; if (MyDT.Rows.Count > 0) { DataRow MyDR = MyDT.Rows[0]; foreach (PropertyInfo MyPro in MyProInfo) { foreach (DataColumn MyDC in MyDT.Columns) { if (MyDC.ColumnName.ToLower() == MyPro.Name.ToLower()) { if (MyDR[MyDC] != System.DBNull.Value) {MyPro.SetValue(model, TypeConvertor.ConvertType(MyDR[MyDC], MyPro.PropertyType), </span><span style="color: #0000ff">null</span><span style="color: #000000">); } </span><span style="color: #0000ff">else</span><span style="color: #000000"> MyPro.SetValue(model, </span><span style="color: #0000ff">null</span>, <span style="color: #0000ff">null</span><span style="color: #000000">); } } } </span><span style="color: #0000ff">return</span><span style="color: #000000"> model; } </span><span style="color: #0000ff">else</span> <span style="color: #0000ff">return</span> <span style="color: #0000ff">default</span><span style="color: #000000">(T);}
- 獲取一個List<Model>
用datareader循環讀取數據庫,然后把每條數據構築一個Model放到List中返回:
/// <summary> /// 通過一個SQL檢索字符串,返回一個List /// </summary> /// <typeparam name="T"></typeparam> /// <param name="sql"></param> /// <returns></returns> public static List<T> DatabaseToModels<T>(string sql) where T : new() { List<T> RtnIl = new List<T>(); SqlDataReader sdr = ExecuteReader(sql); while (sdr.Read()) { T model = new T(); PropertyInfo[] MyProInfo = model.GetType().GetProperties(); foreach (PropertyInfo MyPro in MyProInfo) { for (int i = 0; i < sdr.FieldCount; i++) { if (MyPro.Name.ToLower() == sdr.GetName(i).ToLower()) { if (sdr[i] != System.DBNull.Value) { MyPro.SetValue(model, TypeConvertor.ConvertType(sdr[i], MyPro.PropertyType), null); } else { MyPro.SetValue(model, null, null); } break; } } } RtnIl.Add(model); } sdr.Close(); return RtnIl; }
如此一來,DAL層和BLL層就不會出現和表字段相關的變量。每次修改表后,只需要手動更改Model文件就可以。
如果更進一步偷懶,可以把修改Model寫成一個自動化的過程。每次修改表,代碼生成器自動去修改Model項目文件下的對應類文件。
當然任何事務都是有局限性的。用反射的問題就是性能會帶來一些降低。不過一般系統性能的瓶頸大多數都在數據庫和惡劣的業務算法。相比能帶來開發效率上的提升,我覺得是非常值得的。

![%]YC}%B~WX[UW0781}NF2SG %]YC}%B~WX[UW0781}NF2SG](/image/aHR0cHM6Ly9pbWFnZXMwLmNuYmxvZ3MuY29tL2Jsb2cvMTI2NDUvMjAxNDAzLzI1MTg0NTMxNzAxMjQ2My5qcGc=.png)
![GV$7OB$QCQ~R]PXQ])O[1YH GV$7OB$QCQ~R]PXQ])O[1YH](/image/aHR0cHM6Ly9pbWFnZXMwLmNuYmxvZ3MuY29tL2Jsb2cvMTI2NDUvMjAxNDAzLzI1MTg0NTMzNTc2NzM4OS5qcGc=.png)