前言
這兩天看園子里有個朋友寫Dapper的拓展,想到自己之前也嘗試用過,但不順手,曾寫過幾個方法來完成自動的Insert操作。而對於Update、Delete、Select等,我一直對Dictionary<string,object>參數類型有很深的感情,旨在頁面傳遞的條件基本上都是Json,很容易用字典類型存儲,想過在遵循某種命名規則的前提下,自動完成查詢,簡單的業務,直接能得到相應條件的數據,為此研究過一段時間,如果需要詳細的了解,請看這里:
看了下文章的時間,嚇我一條,居然在2個月前,事實上,這個念頭在心里也一直有很長時間了,卻覺得自己很難去處理好它,封裝的沒問題,但用起來特別不順手。如今新年來臨,再來挑戰自己。
之前遇到的最大的問題就是處理值與類型的映射。由於頁面傳遞的是Json數據源,后來序列化成Dictionary<string,object>后,很多類型就丟失了。如直接引起程序錯誤的DateTime類型了。原來的處理方式是各種手動的映射,更改數據類型,再提交ado.net操作。
如今查看Poto這個Orm的源代碼,它在實體增刪改的時候將類型緩存起來,而且還有該類型的屬性,在使用的時候,做過幾次轉換,而我像發現新大陸似得,覺得這完全可以解決我的問題呀。於是照搬了它的緩存部分代碼,並結合Dapper實現了實體的增刪改查,當然查詢是通過Dictionary的傳參。
對Dapper和Poto的拓展與修改
1.在Dapper的1219行代碼處有方法 GetCacheInfo,在其中增加IDictionary參數支持。修改后的方法為:
private static CacheInfo GetCacheInfo(Identity identity) { CacheInfo info; if (!TryGetQueryCache(identity, out info)) { info = new CacheInfo(); if (identity.parametersType != null) { if (typeof(IDynamicParameters).IsAssignableFrom(identity.parametersType)) { info.ParamReader = (cmd, obj) => { (obj as IDynamicParameters).AddParameters(cmd, identity); }; } #if !CSHARP30 else if ( //支持傳遞Dictionary<K,V>為參數 typeof(IDictionary).IsAssignableFrom(identity.parametersType) || typeof(IEnumerable<KeyValuePair<string, object>>).IsAssignableFrom(identity.parametersType) && typeof(System.Dynamic.IDynamicMetaObjectProvider).IsAssignableFrom(identity.parametersType) ) { info.ParamReader = (cmd, obj) => { IDynamicParameters mapped = new DynamicParameters(obj); mapped.AddParameters(cmd, identity); }; } #endif else { info.ParamReader = CreateParamInfoGenerator(identity, false); } } SetQueryCache(identity, info); } return info; }
2.在Poto中,增加了OrderAttribute,定義在實體上,指定自動生成的Select排序規則。
再談Orm的具體實現
基於實體的增刪改,參數是object,則可以根據對象獲取到它的類型、屬性,還有定義在類上的Attribute。很容易就可以構造出 insert、update、delete。另外由於Dapper支持一次SQL,在多個對象上執行,我對方法做了這樣的改進,以充分利用Dapper。解釋下,一次SQL,在多個對象上執行,如批量Insert,批量Update,批量Delete。SQL語句使用參數化,則是同一條,執行時參數來自於不同的對象而已。所以增刪改查,參數為object時,還可以傳遞List、數組等。
在Update和Delete方法中,我還希望通過條件來操作,運用到上面說的基於命名規則拼接SQL的方式,當然Select操作也不例外。主要實現代碼:
private readonly Regex reg = new Regex(@"^(?<tab>\w+\.)?(?<Pre>(PK|Begin|End|Like|UnLike|Null|In))?(?<Key>\w+)$"); private string CreateWhere(PocoData t, IDictionary<string, object> condition) { var sql = new StringBuilder(); //遍歷條件 foreach (var key in condition.Keys.ToArray()) { var match = reg.Match(key); //正則獲取到屬性對應的字段名 var pre = match.Groups["Pre"].Value;//獲取命名規則中的比較謂詞 var colName = match.Groups["Key"].Value;//獲取字段名稱 //if (string.IsNullOrEmpty(tableName)) // tableName = match.Groups["tab"].Value;//獲取字段表名限定 var paraName = string.Format("{0}{1}", pre, colName);//獲取該條件被分配的過程參數名 var value = condition[key]; //拼接Sql語句 sql.AppendFormat(" {0} AND", Builder(pre, colName, paraName, t.TableInfo.TableName, value));//拼接SQL條件 if (pre == "In") //In拼接沒有使用過程參數,直接拼接了,將參數從字典中移除 { condition.Remove(key); continue; } //若該字段值的類型為字符串,檢測實體中的類型 if (value.GetType() == typeof(string)) { PocoColumn prop; if (!t.Columns.TryGetValue(colName, out prop)) throw new Exception(string.Format("can't find any property in object.propertyName:{0},object:{1}", key, t.TableInfo.TableName)); if (prop.PropertyInfo.PropertyType != typeof(string)) condition[key] = prop.ChangeType(value); } } if (sql.Length > 3) sql.Length -= 3; return sql.ToString(); }
說到這里,有個小插曲,我發現當我在SQL和Oracle下測試成功后,我傳遞的Dictionary,並沒有指定參數符號,如@或者: 。最后執行的DbCommand對象使用的參數名當然也沒有,可居然沒有出現任何問題。不知道該怎么解釋。這里Sql的庫使用的System.Data.SqlClient, net4.0 。而Oracle是使用的官方的,Oracle.ManagedDataAccess.dll ,net4.0。如果有朋友知道,還請告知一二。雖然我想偷懶,但這種突如其來的幸福,還是警惕下。
最后在Update方法上增加參數,指定更新的列。
其他
解釋下什么是基於單表的Orm。單表操作,增刪改查,所有的方法都是在同一個表或者實體上。由於目前我還沒有看Dapper或者Poco在多表下是怎么操作的,而暫時我又需要緊急的使用在項目中,因為我不想在寫增刪改查,簡單業務下linq式的查詢我也不想寫。好吧,我承認我有點魯莽,不穩定的東西就投入使用,可實踐就是檢驗真理的唯一標准啊。說到實踐,關於Poto這個Orm,我看其他人的介紹,經過各種測試,當我用了,我只想說,這測試不仔細,Oracle下各種錯誤,連自動分頁都是錯的。
寫到這里,發現具體的實現方式,已然不值一提。在寫之前,各種困難,寫之后,想要介紹它的時候,發現太簡單,沒什么可說的。代碼中有一點注釋,想看的朋友如果看不明白可以咨詢。
先貼下封裝后的調用方式。
var db = new DbHelp(Program.facotry, Program.connStr); //增///////////////////////////////////////////////////////////////////// var tabAdd = new tab1() { Name = "ggggg", Home = "jjjjjj" }; var row = db.Add(tabAdd); //批量增 var tabAddArrary = new tab1[] { new tab1() { Name = "批量增1", Home = "批量增1" }, new tab1() { Name = "批量增2", Home = "批量增2" }}; row = db.Add(tabAddArrary); //改////////////////////////////////////////////////////////////////////////// var tabUpdate = new tab1() { Name = "fffffff", Home = "fffffff", ID = 5 }; row = db.Update(tabUpdate); //批量改 var tabUpdateArrary = new tab1[] { new tab1() { Name = "批量改1", Home = "批量改1", ID=10 }, new tab1() { Name = "批量改2", Home = "批量改2", ID=11 }}; row = db.Update(tabUpdateArrary); //條件修改 //1 定義要修改的內容 var dic = new Dictionary<string, object>(); dic["Home"] = DateTime.Now.ToString() + ":修改了"; //定義條件 這里使用了命名規則 var condtion = new Dictionary<string, object>(); condtion["InID"] = "1,3,5"; row = db.Update<tab1>(dic, condtion); //刪除/////////////////////////////////////////////////// var tabDelete = new tab1() { ID = 10 }; db.Remove(tabDelete); //條件刪除 var dicDelete = new Dictionary<string, object>(); dicDelete["BeginID"] = 50; row = db.Remove<tab1>(dicDelete); //查詢///////////////////////////////////////////////////// var dicQuery = new Dictionary<string, object>(); dicQuery["EndID"] = 50; var qtab1 = db.Query<tab1>(dicQuery); //分頁/////////////////////////////////////////////////////////// int count; var dicPage = new Dictionary<string, object>(); dicPage["BeginID"] = 50; dicPage["EndID"] = 10; var qtabPage = db.Query<tab1>(dicQuery, 3, 5, out count);
最后祝大家馬上有錢。
