CPQuery 解決拼接SQL的新方法
我一直都不喜歡在訪問數(shù)據(jù)庫時(shí)采用拼接SQL的方法,原因有以下幾點(diǎn):
1. 不安全:有被SQL注入的風(fēng)險(xiǎn)。
2. 可能會(huì)影響性能:每條SQL語句都需要數(shù)據(jù)庫引擎執(zhí)行[語句分析]之類的開銷。
3. 影響代碼的可維護(hù)性:SQL語句與C#混在一起,想修改SQL就得重新編譯程序,而且二種代碼混在一起,可讀性也不好。
所以我通常會(huì)選擇【參數(shù)化SQL】的方法去實(shí)現(xiàn)數(shù)據(jù)庫的訪問過程,而且會(huì)將SQL語句與項(xiàng)目代碼(C#)分離開。
不過,有些人可能會(huì)說:我的業(yè)務(wù)邏輯很復(fù)雜,Where中的過慮條件不可能事先確定,因此不拼接SQL還不行。
看到這些缺點(diǎn),ORM用戶可能會(huì)認(rèn)為:使用ORM工具就是終極的解決方案。
是的,的確ORM可以解決這些問題。
但是,解決方案并非只有ORM一種,還有些人就是喜歡寫SQL呢。
所以,這篇博客不是寫給ORM用戶的,而是寫給所有喜歡寫SQL語句的朋友。
CPQuery是什么?
看到博客的標(biāo)題,你會(huì)不會(huì)想:CPQuery是什么?
下面是我的回答:
1. CPQuery 是一個(gè)縮寫:Concat Parameterized Query
2. CPQuery 可以讓你繼續(xù)使用熟悉的拼接方式來寫參數(shù)化的SQL
3. CPQuery 是我設(shè)計(jì)的一種解決方案,它可以解決拼接SQL的前二個(gè)缺點(diǎn)。
4. CPQuery 也是這個(gè)解決方案中核心類型的名稱。
希望大家能記住CPQuery這個(gè)名字。
CPQuery適合哪些人使用?
答:適合于喜歡手寫SQL代碼的人,尤其是當(dāng)需要寫動(dòng)態(tài)查詢時(shí)。
參數(shù)化的SQL語句
對(duì)于需要?jiǎng)討B(tài)查詢的場(chǎng)景,我認(rèn)為:拼接SQL或許是必需的,但是,你不要將數(shù)值也拼接到SQL語句中嘛,或者說,你應(yīng)該拼接參數(shù)化的SQL來解決你遇到的問題。
說到【拼接參數(shù)化SQL】,我想解釋一下這個(gè)東西了。
這個(gè)方法的實(shí)現(xiàn)方式是:拼接SQL語句時(shí),不要把參數(shù)值拼接到SQL語句中,在SQL語句中使用占位符參數(shù),具體的參數(shù)值通過ADO.NET的command.Parameters.Add()傳入。現(xiàn)在流行的ORM工具應(yīng)該都會(huì)采用這個(gè)方法。
我認(rèn)為參數(shù)化的SQL語句可以解決本文開頭所說的那些問題,尤其是前二個(gè)。對(duì)于代碼的維護(hù)問題,我的觀點(diǎn)是:如果你硬是將SQL與C#混在一起,那么參數(shù)化的SQL語句也是沒有辦法的。如果想解決這個(gè)問題,你需要將SQL語句與項(xiàng)目代碼分離,然后可以選擇以配置文件或者存儲(chǔ)過程做為保存那些SLQ語句的容器。
所以,參數(shù)化的SQL并不是萬能的,代碼的可維護(hù)性與技術(shù)的選擇無關(guān),與架構(gòu)的設(shè)計(jì)有關(guān)。任何優(yōu)秀的技術(shù)都可能寫出難以維護(hù)的代碼來,這就是我的觀點(diǎn)。
改造現(xiàn)有的拼接語句
還是說動(dòng)態(tài)查詢,假設(shè)我有這樣一個(gè)查詢界面:
顯然,在設(shè)計(jì)程序時(shí),不可能知道用戶會(huì)輸入什么樣的過濾條件。
因此,喜歡手寫SQL的人們通常會(huì)這樣寫查詢:
var query = "select ProductID, ProductName from Products where (1=1) ";
if( p.ProductID > 0 )
query = query + " and ProductID = " + p.ProductID.ToString();
if( string.IsNullOrEmpty(p.ProductName) == false )
query = query + " and ProductName like '" + p.ProductName + "'";
if( p.CategoryID > 0 )
query = query + " and CategoryID = " + p.CategoryID.ToString();
if( string.IsNullOrEmpty(p.Unit) == false )
query = query + " and Unit = '" + p.Unit + "'";
if( p.UnitPrice > 0 )
query = query + " and UnitPrice >= " + p.UnitPrice.ToString();
if( p.Quantity > 0 )
query = query + " and Quantity >= " + p.Quantity.ToString();
如果使用這種方式,本文開頭所說的前二個(gè)缺點(diǎn)肯定是存在的。
我想很多人應(yīng)該是知道參數(shù)化查詢的,最終放棄或許有以下2個(gè)原因:
1. 這種拼接SQL語句的方式很簡(jiǎn)單,非常容易實(shí)現(xiàn)。
2. 便于包裝自己的API,參數(shù)只需要一個(gè)(萬能的)字符串!
如果你認(rèn)為這2個(gè)原因很難解決的話,那我今天就給你 “一種改動(dòng)極小卻可以解決上面二個(gè)缺點(diǎn)”的解決方案,改造后的代碼如下:
var query = "select ProductID, ProductName from Products where (1=1) ".AsCPQuery(true);
if( p.ProductID > 0 )
query = query + " and ProductID = " + p.ProductID.ToString();
if( string.IsNullOrEmpty(p.ProductName) == false )
query = query + " and ProductName like '" + p.ProductName + "'";
if( p.CategoryID > 0 )
query = query + " and CategoryID = " + p.CategoryID.ToString();
if( string.IsNullOrEmpty(p.Unit) == false )
query = query + " and Unit = '" + p.Unit + "'";
if( p.UnitPrice > 0 )
query = query + " and UnitPrice >= " + p.UnitPrice.ToString();
if( p.Quantity > 0 )
query = query + " and Quantity >= " + p.Quantity.ToString();
你看到差別了嗎?
差別在于第一行代碼,后面調(diào)用了一個(gè)擴(kuò)展方法:AsCPQuery(true) ,這個(gè)方法的實(shí)現(xiàn)代碼我后面再說。
這個(gè)示例的主要關(guān)鍵代碼如下:
private static readonly string ConnectionString =
ConfigurationManager.ConnectionStrings["MyNorthwind_MSSQL"].ConnectionString;
private void btnQuery_Click(object sender, EventArgs e)
{
Product p = new Product();
p.ProductID = SafeParseInt(txtProductID.Text);
p.ProductName = txtProductName.Text.Trim();
p.CategoryID = SafeParseInt(txtCategoryID.Text);
p.Unit = txtUnit.Text.Trim();
p.UnitPrice = SafeParseDecimal(txtUnitPrice.Text);
p.Quantity = SafeParseInt(txtQuantity.Text);
var query = BuildDynamicQuery(p);
try {
txtOutput.Text = ExecuteQuery(query);
}
catch( Exception ex ) {
txtOutput.Text = ex.Message;
}
}
private CPQuery BuildDynamicQuery(Product p)
{
var query = "select ProductID, ProductName from Products where (1=1) ".AsCPQuery(true);
if( p.ProductID > 0 )
query = query + " and ProductID = " + p.ProductID.ToString();
if( string.IsNullOrEmpty(p.ProductName) == false )
query = query + " and ProductName like '" + p.ProductName + "'";
if( p.CategoryID > 0 )
query = query + " and CategoryID = " + p.CategoryID.ToString();
if( string.IsNullOrEmpty(p.Unit) == false )
query = query + " and Unit = '" + p.Unit + "'";
if( p.UnitPrice > 0 )
query = query + " and UnitPrice >= " + p.UnitPrice.ToString();
if( p.Quantity > 0 )
query = query + " and Quantity >= " + p.Quantity.ToString();
return query;
}
private string ExecuteQuery(CPQuery query)
{
StringBuilder sb = new StringBuilder();
using( SqlConnection connection = new SqlConnection(ConnectionString) ) {
SqlCommand command = connection.CreateCommand();
// 將前面的拼接結(jié)果綁定到命令對(duì)象。
query.BindToCommand(command);
// 輸出調(diào)試信息。
sb.AppendLine("==================================================");
sb.AppendLine(command.CommandText);
foreach( SqlParameter p in command.Parameters )
sb.AppendFormat("{0} = {1}\r\n", p.ParameterName, p.Value);
sb.AppendLine("==================================================\r\n");
// 打開連接,執(zhí)行查詢
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while( reader.Read() )
sb.AppendFormat("{0}, {1}\r\n", reader[0], reader[1]);
}
return sb.ToString();
}
private int SafeParseInt(string s)
{
int result = 0;
int.TryParse(s, out result);
return result;
}
private decimal SafeParseDecimal(string s)
{
decimal result = 0m;
decimal.TryParse(s, out result);
return result;
}
我們來看一下程序運(yùn)行的結(jié)果:
根據(jù)前面給出的調(diào)試代碼:
// 輸出調(diào)試信息。
sb.AppendLine("==================================================");
sb.AppendLine(command.CommandText);
foreach( SqlParameter p in command.Parameters )
sb.AppendFormat("{0} = {1}\r\n", p.ParameterName, p.Value);
sb.AppendLine("==================================================\r\n");
以及圖片反映的事實(shí),可以得出結(jié)論:改造后的查詢已經(jīng)是參數(shù)化的查詢了!
揭秘原因
是不是很神奇:加了一個(gè)AsCPQuery()的調(diào)用,就將原來的拼接SQL變成了參數(shù)化查詢?
這其中的原因有以下幾點(diǎn):
1. AsCPQuery()的調(diào)用產(chǎn)生了一個(gè)新的對(duì)象,它的類型不是string,而是CPQuery
2. 在每次執(zhí)行 + 運(yùn)算符時(shí),已經(jīng)不再是二個(gè)string對(duì)象的相加。
3. CPQuery重載了 + 運(yùn)算符,會(huì)識(shí)別拼接過程中的參數(shù)值與SQL語句片段。
4. 查詢構(gòu)造完成后,得到的結(jié)果不再是一個(gè)字符串,而是一個(gè)CPQuery對(duì)象,它可以生成參數(shù)化的SQL語句,它還包含了所有的參數(shù)值。
AsCPQuery()是一個(gè)擴(kuò)展方法,代碼:
public static CPQuery AsCPQuery(this string s)
{
return new CPQuery(s, false);
}
public static CPQuery AsCPQuery(this string s, bool autoDiscoverParameters)
{
return new CPQuery(s,autoDiscoverParameters);
}
所以在調(diào)用后,會(huì)得到一個(gè)CPQuery對(duì)象。
觀察前面的示例代碼,你會(huì)發(fā)現(xiàn)AsCPQuery()只需要調(diào)用一次。
要得到一個(gè)CPQuery對(duì)象,也可以調(diào)用CPQuery類型的靜態(tài)方法:
public static CPQuery New()
{
return new CPQuery(null, false);
}
public static CPQuery New(bool autoDiscoverParameters)
{
return new CPQuery(null, autoDiscoverParameters);
}
這二種方法是等效的,示例代碼:
// 下面二行代碼是等價(jià)的,可根據(jù)喜好選擇。
var query = "select ProductID, ProductName from Products where (1=1) ".AsCPQuery();
//var query = CPQuery.New() + "select ProductID, ProductName from Products where (1=1) ";
繼續(xù)看拼接的處理:
public static CPQuery operator +(CPQuery query, string s)
{
query.AddSqlText(s);
return query;
}
CPQuery重載了 + 運(yùn)算符,所以,結(jié)果已經(jīng)不再是二個(gè)string對(duì)象的相加的結(jié)果,而是CPQuery對(duì)象本身(JQuery的鏈接設(shè)計(jì)思想,便于繼續(xù)拼接)。
思考一下: " where id = " + "234" + "…………"
你認(rèn)為我是不是可以判斷出 234 就是一個(gè)參數(shù)值?
類似的還有:" where name = '" + "Fish Li" + "'"
顯然,"Fish Li"就是表示一個(gè)字符串的參數(shù)值嘛,因?yàn)槠唇拥淖笥叶叾加?' 包圍著。
所以,CPQuery對(duì)象會(huì)識(shí)別拼接過程中的參數(shù)值與SQL語句片段。
查詢拼接完成了,但是此時(shí)的SQL語句保存在CPQuery對(duì)象中,而且不可能通過一個(gè)字符串的方式返回,因?yàn)檫€可能包含多個(gè)查詢參數(shù)呢。所以,在執(zhí)行查詢時(shí),相關(guān)的方法需要能夠接收CPQuery對(duì)象,例如:
static string ExecuteQuery(CPQuery query)
{
StringBuilder sb = new StringBuilder();
using( SqlConnection connection = new SqlConnection(ConnectionString) ) {
SqlCommand command = connection.CreateCommand();
// 將前面的拼接結(jié)果綁定到命令對(duì)象。
query.BindToCommand(command);
一旦調(diào)用了query.BindToCommand(command); CPQuery對(duì)象會(huì)把它在內(nèi)部拼接的參數(shù)化SQL,以及收集的所有參數(shù)值賦值給command對(duì)象。后面的事情,該怎么做就怎么做吧,我想大家都會(huì),就不再多說了。
CPQuery源碼
前面只貼出了CPQuery的部分代碼,這里給出相關(guān)的全部代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
namespace CPQueryDEMO
{
public sealed class CPQuery
{
private enum SPStep // 字符串參數(shù)的處理進(jìn)度
{
NotSet, // 沒開始或者已完成一次字符串參數(shù)的拼接。
EndWith, // 拼接時(shí)遇到一個(gè)單引號(hào)結(jié)束
Skip // 已跳過一次拼接
}
private int _count;
private StringBuilder _sb = new StringBuilder(1024);
private Dictionary<string, QueryParameter> _parameters = new Dictionary<string, QueryParameter>(10);
private bool _autoDiscoverParameters;
private SPStep _step = SPStep.NotSet;
public CPQuery(string text, bool autoDiscoverParameters)
{
_sb.Append(text); _autoDiscoverParameters = autoDiscoverParameters;
}
public static CPQuery New()
{
return new CPQuery(null, false);
}
public static CPQuery New(bool autoDiscoverParameters)
{
return new CPQuery(null, autoDiscoverParameters);
}
public override string ToString()
{
return _sb.ToString();
}
public void BindToCommand(DbCommand command)
{
if( command == null )
throw new ArgumentNullException("command");
command.CommandText = _sb.ToString();
command.Parameters.Clear();
foreach( KeyValuePair<string, QueryParameter> kvp in _parameters ) {
DbParameter p = command.CreateParameter();
p.ParameterName = kvp.Key;
p.Value = kvp.Value.Value;
command.Parameters.Add(p);
}
}
private void AddSqlText(string s)
{
if( string.IsNullOrEmpty(s) )
return;
if( _autoDiscoverParameters ) {
if( _step == SPStep.NotSet ) {
if( s[s.Length - 1] == '\'' ) { // 遇到一個(gè)單引號(hào)結(jié)束
_sb.Append(s.Substring(0, s.Length - 1));
_step = SPStep.EndWith; } else {
object val = TryGetValueFromString(s);
if( val == null )
_sb.Append(s);
else
this.AddParameter(val.AsQueryParameter());
}
}
else if( _step == SPStep.EndWith ) {
// 此時(shí)的s應(yīng)該是字符串參數(shù),不是SQL語句的一部分
// _step 在AddParameter方法中統(tǒng)一修改,防止中途拼接非字符串?dāng)?shù)據(jù)。
this.AddParameter(s.AsQueryParameter());
}
else {
if( s[0] != '\'' )
throw new ArgumentException("正在等待以單引號(hào)開始的字符串,但參數(shù)不符合預(yù)期格式。");
// 找到單引號(hào)的閉合輸入。
_sb.Append(s.Substring(1));
_step = SPStep.NotSet;
}
}
else {
// 不檢查單引號(hào)結(jié)尾的情況,此時(shí)認(rèn)為一定是SQL語句的一部分。
_sb.Append(s);
}
}
private void AddParameter(QueryParameter p)
{
if( _autoDiscoverParameters && _step == SPStep.Skip )
throw new InvalidOperationException("正在等待以單引號(hào)開始的字符串,此時(shí)不允許再拼接其它參數(shù)。");
string name = "@p" + (_count++).ToString();
_sb.Append(name);
_parameters.Add(name, p);
if( _autoDiscoverParameters && _step == SPStep.EndWith )
_step = SPStep.Skip;
}
private object TryGetValueFromString(string s)
{
// 20,可以是byte, short, int, long, uint, ulong ...
int number1 = 0;
if( int.TryParse(s, out number1) )
return number1;
DateTime dt = DateTime.MinValue;
if( DateTime.TryParse(s, out dt) )
return dt;
// 23.45,可以是float, double, decimal
decimal number5 = 0m;
if( decimal.TryParse(s, out number5) )
return number5;
// 其它類型全部放棄嘗試。
return null;
}
public static CPQuery operator +(CPQuery query, string s)
{
query.AddSqlText(s);
return query;
}
public static CPQuery operator +(CPQuery query, QueryParameter p)
{
query.AddParameter(p);
return query;
}
}
public sealed class QueryParameter
{
private object _val;
public QueryParameter(object val)
{
_val = val;
}
public object Value
{
get { return _val; }
}
public static explicit operator QueryParameter(string a)
{
return new QueryParameter(a);
}
public static implicit operator QueryParameter(int a)
{
return new QueryParameter(a);
}
public static implicit operator QueryParameter(decimal a)
{
return new QueryParameter(a);
}
public static implicit operator QueryParameter(DateTime a)
{
return new QueryParameter(a);
}
// 其它需要支持的隱式類型轉(zhuǎn)換操作符重載請(qǐng)自行添加。
}
public static class CPQueryExtensions
{
public static CPQuery AsCPQuery(this string s)
{
return new CPQuery(s, false);
}
public static CPQuery AsCPQuery(this string s, bool autoDiscoverParameters)
{
return new CPQuery(s,autoDiscoverParameters);
}
public static QueryParameter AsQueryParameter(this object b)
{
return new QueryParameter(b);
}
}
}
CPQuery的已知問題以及解決方法
在開始閱讀這一節(jié)之前,請(qǐng)務(wù)必保證已經(jīng)閱讀過前面的源代碼,尤其是AddSqlText,TryGetValueFromString這二個(gè)方法。在【揭秘原因】這節(jié)中,我說過:CPQuery重載了 + 運(yùn)算符,會(huì)識(shí)別拼接過程中的參數(shù)值與SQL語句片段。 其實(shí)這個(gè)所謂的識(shí)別過程,主要就是在這二個(gè)方法中實(shí)現(xiàn)的。
尤其是在TryGetValueFromString方法中,我無奈地寫出了下面的注釋:
// 20,可以是byte, short, int, long, uint, ulong ...
// 23.45,可以是float, double, decimal
// 其它類型全部放棄嘗試。
很顯然,當(dāng)把一個(gè)數(shù)字變成字符串后,很難再知道數(shù)字原來的類型是什么。
因此,在這個(gè)方法的實(shí)現(xiàn)過程中,我只使用了我認(rèn)為最常見的數(shù)據(jù)類型。
我不能保證它們永遠(yuǎn)能夠正確運(yùn)行。
還有,雖然我們可以通過判斷二個(gè) ' 來確定中間是一個(gè)字符串參數(shù)值,然而,對(duì)于前面的示例中的參數(shù)值來說:"Fish Li" 這個(gè)字符串如果是寫成這樣呢:"Fish" + " " + "Li" ?因?yàn)楹苡锌赡軐?shí)際代碼是:s1 + " " + s2,換句話說:字符串參數(shù)值也是拼接得到的。
對(duì)于這二個(gè)問題,我只能說:我也沒辦法了。
這是一個(gè)已知道問題,那么有沒有解決方法呢?
答案是:有的。思路也簡(jiǎn)單:既然猜測(cè)可能會(huì)出錯(cuò),那么就不要去猜了,你得顯式指出參數(shù)值。
如何【顯式指出參數(shù)值】呢?
其實(shí)也不難,大致有以下方法:
1. 非字符串參數(shù)值不要轉(zhuǎn)成字符串,例如:數(shù)字就讓它是數(shù)字。
2. 字符串參數(shù)需要單獨(dú)標(biāo)識(shí)出來。
具體方法可參考下面的示例代碼(與前面的代碼是等價(jià)的):
static CPQuery BuildDynamicQuery(Product p)
{
// 下面二行代碼是等價(jià)的,可根據(jù)喜好選擇。
var query = "select ProductID, ProductName from Products where (1=1) ".AsCPQuery();
//var query = CPQuery.New() + "select ProductID, ProductName from Products where (1=1) ";
// 注意:下面的拼接代碼中不能寫成: query += .....
if( p.ProductID > 0 )
query = query + " and ProductID = " + p.ProductID; // 整數(shù)參數(shù)。
if( string.IsNullOrEmpty(p.ProductName) == false )
// 給查詢添加一個(gè)字符串參數(shù)。
query = query + " and ProductName like " + p.ProductName.AsQueryParameter();
if( p.CategoryID > 0 )
query = query + " and CategoryID = " + p.CategoryID; // 整數(shù)參數(shù)。
if( string.IsNullOrEmpty(p.Unit) == false )
query = query + " and Unit = " + (QueryParameter)p.Unit; // 字符串參數(shù)
if( p.UnitPrice > 0 )
query = query + " and UnitPrice >= " + p.UnitPrice; // decimal參數(shù)。
if( p.Quantity > 0 )
query = query + " and Quantity >= " + p.Quantity; // 整數(shù)參數(shù)。
return query;
}
在這段代碼中,數(shù)字沒有轉(zhuǎn)成字符串,它在運(yùn)行時(shí),其實(shí)是執(zhí)行QueryParameter類型中定義的隱式類型轉(zhuǎn)換,它們會(huì)轉(zhuǎn)換成QueryParameter對(duì)象,因此,根本就沒有機(jī)會(huì)搞錯(cuò),而且執(zhí)行效率更高。字符串參數(shù)值需要調(diào)用AsQueryParameter()擴(kuò)展方法或者顯式轉(zhuǎn)換成QueryParameter對(duì)象,此時(shí)也不需要識(shí)別,因此也沒機(jī)會(huì)搞錯(cuò)。
我強(qiáng)烈推薦使用這種方法來拼接。
注意:
1. 字符串參數(shù)值在拼接時(shí),不需要由二個(gè) ' 包起來。
2. AsCPQuery()或者CPQuery.New()的調(diào)用中,不需要參數(shù),或者傳入false 。
說明:
1. 在拼接字符串時(shí),C#本身就允許 "abc" + 123 這樣的寫法,只是說寫成"abc" + 123.ToString()會(huì)快點(diǎn)。
2. 在使用CPQuery時(shí),所有的參數(shù)值都可以顯式轉(zhuǎn)換成QueryParameter,例如:“……” + (QueryParameter)p.Quantity
更多CPQuery示例
CPQuery是為了部分解決拼接SQL的缺點(diǎn)而設(shè)計(jì)的,它做為ClownFish的增強(qiáng)功能已補(bǔ)充到ClownFish中。
在ClownFish的示例中,也專門為CPQuery準(zhǔn)備了一個(gè)更強(qiáng)大的示例,那個(gè)示例演示了在4種數(shù)據(jù)庫中使用CPQuery:
為了方便的使用CPQuery,ClownFish的DbHelper類為所有的數(shù)據(jù)庫訪問方法提供了對(duì)應(yīng)的重載方法:
public static int ExecuteNonQuery(CPQuery query)
public static int ExecuteNonQuery(CPQuery query, DbContext dbContext)
public static object ExecuteScalar(CPQuery query)
public static object ExecuteScalar(CPQuery query, DbContext dbContext)
public static T ExecuteScalar<T>(CPQuery query)
public static T ExecuteScalar<T>(CPQuery query, DbContext dbContext)
public static T GetDataItem<T>(CPQuery query)
public static T GetDataItem<T>(CPQuery query, DbContext dbContext)
public static List<T> FillList<T>(CPQuery query)
public static List<T> FillList<T>(CPQuery query, DbContext dbContext)
public static List<T> FillScalarList<T>(CPQuery query)
public static List<T> FillScalarList<T>(CPQuery query, DbContext dbContext)
public static DataTable FillDataTable(CPQuery query)
public static DataTable FillDataTable(CPQuery query, DbContext dbContext)
所以,使用起來也非常容易:
var query = BuildDynamicQuery(p);
DataTable table = DbHelper.FillDataTable(query);
CPQuery的設(shè)計(jì)目標(biāo)及使用建議
CPQuery的設(shè)計(jì)目標(biāo)是:將傳統(tǒng)的拼接SQL代碼轉(zhuǎn)成參數(shù)化的SQL,而且將使用和學(xué)習(xí)成本降到最低。
本文開頭的示例我想已經(jīng)證明了CPQuery已經(jīng)實(shí)現(xiàn)了這個(gè)目標(biāo)。
只需要拼接的第一個(gè)字符串上調(diào)用AsCPQuery()擴(kuò)展方法,或者在所有字符串前加上CPQuery.New()就能解決。
注意:
1. 提供AsCPQuery(true)或者CPQuery.New(true)方法,僅僅用于處理現(xiàn)有代碼,可認(rèn)為是兼容性解決方案。
2. 我強(qiáng)烈建議調(diào)用AsCPQuery()或者CPQuery.New()來處理拼接,原因前面有解釋,這里不再重復(fù)。
有些人看到了示例代碼會(huì)認(rèn)為CPQuery使用起來好復(fù)雜。這種說法完全是不動(dòng)腦子的說法。
你寫拼接SQL的代碼會(huì)短多少?
我前面已經(jīng)說過了:CPQuery的設(shè)計(jì)目標(biāo)不是一個(gè)數(shù)據(jù)訪問層,它只是為解決拼接SQL而設(shè)計(jì)的。
使用起來方不方便,要看具體的數(shù)據(jù)訪問層來與CPQuery的整體與包裝方式。
示例代碼為了保證所有人能看懂,我直接使用了ADO.NET,而且中間包含了調(diào)試代碼,所以看起來長(zhǎng)了點(diǎn),但是,關(guān)鍵代碼有多少,這個(gè)還看不出來嗎?
CPQuery類的代碼,你看不懂也沒用關(guān)系,我們只需要調(diào)用一次它的擴(kuò)展方法(或者靜態(tài)方法)就可以了。
關(guān)于易用性,我最后想說的就是:如果想方便,可以試一下 ClownFish,它集成了CPQuery 。
友情提示
本文一開始,我就明確表達(dá)了我的觀點(diǎn):CPQuery僅能解決拼接SQL的前二個(gè)缺點(diǎn)。
應(yīng)該僅當(dāng)需要實(shí)現(xiàn)動(dòng)態(tài)查詢時(shí)才使用CPQuery,因?yàn)槠唇訒?huì)涉及多種語句的代碼混合在一起,這種做法會(huì)給代碼的可維護(hù)性產(chǎn)生負(fù)面影響。
點(diǎn)擊此處下載CPQuery源碼和示例代碼
- sqlserver 存儲(chǔ)過程動(dòng)態(tài)參數(shù)調(diào)用實(shí)現(xiàn)代碼
- 一次性壓縮Sqlserver2005中所有庫日志的存儲(chǔ)過程
- C#拼接SQL語句 用ROW_NUMBER實(shí)現(xiàn)的高效分頁排序
- sqlserver 存儲(chǔ)過程帶事務(wù) 拼接id 返回值
- sqlserver 各種判斷是否存在(表名、函數(shù)、存儲(chǔ)過程等)
- SqlServer獲取存儲(chǔ)過程返回值的實(shí)例
- sqlserver2008查看表記錄或者修改存儲(chǔ)過程出現(xiàn)目錄名無效錯(cuò)誤解決方法
- sqlserver2005利用臨時(shí)表和@@RowCount提高分頁查詢存儲(chǔ)過程性能示例分享
- 使用sqlserver存儲(chǔ)過程sp_send_dbmail發(fā)送郵件配置方法(圖文)
- sqlserver存儲(chǔ)過程語法詳解
- SQLServer用存儲(chǔ)過程實(shí)現(xiàn)插入更新數(shù)據(jù)示例
- 談?wù)剆qlserver自定義函數(shù)與存儲(chǔ)過程的區(qū)別
- SqlServer存儲(chǔ)過程實(shí)現(xiàn)及拼接sql的注意點(diǎn)
相關(guān)文章
sql時(shí)間格式化輸出、Convert函數(shù)應(yīng)用示例
這篇文章主要介紹了sql時(shí)間格式化輸出、Convert函數(shù)應(yīng)用,需要的朋友可以參考下2014-03-03SQL Server怎么找出一個(gè)表包含的頁信息(Page)
這篇文章主要給大家介紹了關(guān)于SQL Server是如何找出一個(gè)表包含的頁信息(Page)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用SQL Server具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10如何區(qū)分SQL數(shù)據(jù)庫中的主鍵與外鍵
這篇文章主要介紹了如何分清SQL數(shù)據(jù)庫中的主鍵與外鍵,這里簡(jiǎn)單介紹下,方便需要的朋友2013-06-06世界杯猜想活動(dòng)的各類榜單的SQL語句小結(jié)
自己網(wǎng)站的世界杯猜想活動(dòng),整理了幾個(gè)排行榜。寫了半個(gè)小時(shí)的SQL,丟了多可惜,放在這里,反正是別人的地盤,不心疼。2010-07-07SQL update 多表關(guān)聯(lián)更新的實(shí)現(xiàn)代碼
這篇文章主要介紹了SQL update 多表關(guān)聯(lián)更新的實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-09-09SQL點(diǎn)滴24 監(jiān)測(cè)表的變化
在網(wǎng)上看到一篇關(guān)于監(jiān)測(cè)表中的插入,更新,刪除的方法,使用觸發(fā)器實(shí)現(xiàn)的,很有價(jià)值。2011-09-09SQL?Server?2008?R2完美卸載教程(親測(cè)有用)
SQL Server 2008 R2是一款非常強(qiáng)大的數(shù)據(jù)庫管理系統(tǒng),但在某些情況下可能需要卸載它,下面這篇文章主要給大家介紹了關(guān)于SQL?Server?2008?R2完美卸載的相關(guān)資料,需要的朋友可以參考下2023-11-11