LINQ to SQL:如何直接執(zhí)行SQL語句
1、ExecuteQuery方法
看命名,我們很容易聯(lián)想到ADO.NET里熟悉的Command的ExecuteNonQuery方法,但是VS的智能提示告訴我們這個(gè)方法返回的是一個(gè)泛型集合,應(yīng)該“所思非所得”。下面通過一個(gè)簡單方法,驗(yàn)證我們的猜想(數(shù)據(jù)庫設(shè)計(jì)可以參考這一篇):
- /// <summary>
- /// 直接執(zhí)行sql語句,獲取總?cè)藬?shù)
- /// </summary>
- /// <returns></returns>
- public int GetTotalCount()
- {
- string strSql = "SELECT COUNT(0) FROM Person(NOLOCK)";
- var query = dataContext.ExecuteQuery<int>(strSql);
- int result = query.First<int>();
- Console.WriteLine();
- Console.WriteLine("total count:{0}", result);
- return result;
- }
調(diào)試的時(shí)候,通過IntelliTrace跟蹤到:
毫無疑問,上面的圖片說明最初的想法是不正確的,”ADO.NET:執(zhí)行Reader…”云云,讓我們更加堅(jiān)信它實(shí)際執(zhí)行的應(yīng)該是ExecuteReader方法。當(dāng)然最簡單的方法是直接查看它的方法說明:
- // 摘要:
- // 直接對(duì)數(shù)據(jù)庫執(zhí)行 SQL 查詢并返回對(duì)象。
- //
- // 參數(shù):
- // query:
- // 要執(zhí)行的 SQL 查詢。
- //
- // parameters:
- // 要傳遞給命令的參數(shù)數(shù)組。注意下面的行為:如果數(shù)組中的對(duì)象的數(shù)目小于命令字符串中已標(biāo)識(shí)的最大數(shù),
- 則會(huì)引發(fā)異常。如果數(shù)組包含未在命令字符串中引用的對(duì)象,則不會(huì)引發(fā)異常。如果某參數(shù)為
- // null,則該參數(shù)會(huì)轉(zhuǎn)換為 DBNull.Value。
- //
- // 類型參數(shù):
- // TResult:
- // 返回的集合中的元素的類型。
- //
- // 返回結(jié)果:
- // 由查詢返回的對(duì)象的集合。
- public IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters);
ExecuteQuery方法還有一個(gè)非泛型方法:
- //
- // 摘要:
- // 直接對(duì)數(shù)據(jù)庫執(zhí)行 SQL 查詢。
- //
- // 參數(shù):
- // elementType:
- //
要返回的 System.Collections.Generic.IEnumerable<T> 的類型。使查詢結(jié)果中的列與對(duì)象中的字段或?qū)傩韵嗥ヅ涞乃惴ㄈ缦滤荆喝绻侄位驅(qū)傩杂成涞教囟忻Q,則結(jié)果集中應(yīng)包含該列名稱。如果未映射字段或?qū)傩?,則結(jié)果集中應(yīng)包含其名稱與該字段或?qū)傩韵嗤牧?。通過先查找區(qū)分大小寫的匹配來執(zhí)行比較。如果未找到匹配項(xiàng),則會(huì)繼續(xù)搜索不區(qū)分大小寫的匹配項(xiàng)。如果同時(shí)滿足下列所有條件,則該查詢應(yīng)當(dāng)返回(除延遲加載的對(duì)象外的)對(duì)象的所有跟蹤的字段和屬性:T
- // 是由 System.Data.Linq.DataContext 顯式跟蹤的實(shí)體。
- System.Data.Linq.DataContext.ObjectTrackingEnabled
- // 為 true。實(shí)體具有主鍵。否則會(huì)引發(fā)異常。
- //
- // query:
- // 要執(zhí)行的 SQL 查詢。
- //
- // parameters:
- // 要傳遞給命令的參數(shù)數(shù)組。注意下面的行為:如果數(shù)組中的對(duì)象的數(shù)目小于命令字符串中已標(biāo)識(shí)的最大數(shù),
- 則會(huì)引發(fā)異常。如果數(shù)組包含未在命令字符串中引用的對(duì)象,則不會(huì)引發(fā)異常。如果某參數(shù)為
- // null,則該參數(shù)會(huì)轉(zhuǎn)換為 DBNull.Value。
- //
- // 返回結(jié)果:
- // 由查詢返回的對(duì)象的 System.Collections.Generic.IEnumerable<T> 集合。
- public IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters);
看它的參數(shù)需要多傳遞一個(gè)elementType,明顯不如泛型方法簡潔。
2、ExecuteCommand方法
同樣道理,這個(gè)方法立刻讓我們聯(lián)想到(世界沒有聯(lián)想,生活將會(huì)怎樣?),聯(lián)想到,等等,不知聯(lián)想到什么。然后我們看一下方法使用說明:
- //
- // 摘要:
- // 直接對(duì)數(shù)據(jù)庫執(zhí)行 SQL 命令。
- //
- // 參數(shù):
- // command:
- // 要執(zhí)行的 SQL 命令。
- //
- // parameters:
- // 要傳遞給命令的參數(shù)數(shù)組。注意下面的行為:如果數(shù)組中的對(duì)象的數(shù)目小于命令字符串中已標(biāo)識(shí)的最大數(shù),
- 則會(huì)引發(fā)異常。如果數(shù)組包含未在命令字符串中引用的對(duì)象,則不會(huì)引發(fā)異常。如果任一參數(shù)為
- // null,則該參數(shù)會(huì)轉(zhuǎn)換為 DBNull.Value。
- //
- // 返回結(jié)果:
- // 一個(gè) int,表示所執(zhí)行命令修改的行數(shù)。
- public int ExecuteCommand(string command, params object[] parameters);
到這里,看它的返回類型為int,表示執(zhí)行命令修改的行數(shù),這次很容易想到ExecuteNonQuery方法。對(duì)不對(duì)呢?通過下面的代碼證明我們的設(shè)想:
- /// <summary>
- /// 直接執(zhí)行sql語句 根據(jù)用戶Id更新體重
- /// </summary>
- /// <param name="id">用戶Id</param>
- /// <param name="destWeight">更新后的體重</param>
- /// <returns></returns>
- public int ModifyWeightById(int id, double destWeight)
- {
- string strSql = string.Format("UPDATE Person SET Weight={0} WHERE Id={1}", destWeight, id);
- int result = dataContext.ExecuteCommand(strSql);
- Console.WriteLine();
- Console.WriteLine("affect num:{0}", result);
- return result;
- }
調(diào)試過程中,通過IntelliTrace可以很清楚地捕獲:
“ADO.NET:執(zhí)行NonQuery…”基本可以斷言我們的設(shè)想是正確的。
3、防止sql注入
1和2中,執(zhí)行sql語句的兩個(gè)方法都有一個(gè)params 類型的參數(shù),我們又會(huì)想到ADO.NET非常重要的sql語句的參數(shù)化防止sql注入問題。下面通過一個(gè)方法,看看linq2sql可不可以防止sql注入。
(1)、直接執(zhí)行拼接的sql語句(有風(fēng)險(xiǎn))
- /// <summary>
- /// 直接執(zhí)行sql語句 根據(jù)用戶Id更新FirstName
- /// </summary>
- /// <param name="id">用戶Id</param>
- /// <param name="destName">更新后的FirstName</param>
- /// <returns></returns>
- public int ModifyNameById(int id, string destName)
- {
- string strSql = string.Format("UPDATE Person SET FirstName='{0}' WHERE Id={1}", destName, id);
- //這么拼接有風(fēng)險(xiǎn)
- int result = dataContext.ExecuteCommand(strSql);
- Console.WriteLine();
- Console.WriteLine("affect num:{0}", result);
- return result;
- }
然后,在客戶端這樣調(diào)用這個(gè)方法:
- int result = ServiceFactory.CreatePersonService().ModifyNameById(10, "'Anders'");
- //更新id為10的人的FirstName
運(yùn)行的時(shí)候,直接拋出異常,提示“Anders”附近有語法錯(cuò)誤。毫無疑問,它執(zhí)行的sql語句:
- UPDATE Person SET FirstName=''Anders'' WHERE Id=10
是不能通過的,同時(shí)證明了string.format函數(shù)不能有效防止sql注入。
(2)、直接通過linq方法的參數(shù)傳遞,結(jié)果如何呢?
改進(jìn)(1)的傳參方法:
- /// <summary>
- /// 直接執(zhí)行sql語句 根據(jù)用戶Id更新FirstName
- /// </summary>
- /// <param name="id">用戶Id</param>
- /// <param name="destName">更新后的FirstName</param>
- /// <returns></returns>
- public int ModifyNameById(int id, string destName)
- {
- //string strSql = string.Format("UPDATE Person SET FirstName='{0}' WHERE Id={1}", destName, id);
- //這么拼接有風(fēng)險(xiǎn)
- //int result = dataContext.ExecuteCommand(strSql);
- string strSql = "UPDATE Person SET FirstName={0} WHERE Id={1}";
- int result = dataContext.ExecuteCommand(strSql, new object[] { destName, id });
- Console.WriteLine();
- Console.WriteLine("affect num:{0}", result);
- return result;
- }
再次通過如下的方式調(diào)用:
- int result = ServiceFactory.CreatePersonService().ModifyNameById(10, "'Anders'");
- //更新id為10的人的FirstName
這一次運(yùn)行正常嗎?經(jīng)測試,真的真的是真的正常,終于可以長松一口氣了。查看數(shù)據(jù)庫,Id為10的那一條記錄的FirstName結(jié)果改變成了“'Anders'”(帶單引號(hào)),明白ADO.NET工作原理的都知道這個(gè)一點(diǎn)也不神奇,不是嗎?
對(duì)比(1)和(2),我們發(fā)現(xiàn)還是用linq2sql的源生方法傳參比較好,通過拼接sql應(yīng)該盡量避免。
到這里,可能你會(huì)認(rèn)為上面1、2、3全是廢話,知道怎么用不就行了嘛?!其實(shí)很長一部分時(shí)間我也是這么想這么做的。工作久了,早就習(xí)慣了不求甚解,現(xiàn)在看書,看園子里高手的博客,多多少少會(huì)有點(diǎn)反思。
4、思考
ADO.NET中比較常用的ExecuteScalar方法,在linq2sql應(yīng)該怎么實(shí)現(xiàn)呢?您嘗試使用過了嗎?
原文鏈接:http://www.cnblogs.com/jeffwongishandsome/archive/2010/11/03/1868438.html
【編輯推薦】