这篇文章主要为大家详细介绍了SQL Server CPQuery 解决拼接SQL的新方法,具有一定的参考价值,可以用来参考一下。
感兴趣的小伙伴,下面一起跟随四海网的小编两巴掌来看看吧!
我一直都不喜欢在访问数据库时采用拼接SQL的方法,原因有以下几点:
1. 不安全:有被SQL注入的风险。
2. 可能会影响性能:每条SQL语句都需要数据库引擎执行[语句分析]之类的开销。
3. 影响代码的可维护性:SQL语句与C#混在一起,想修改SQL就得重新编译程序,而且二种代码混在一起,可读性也不好。
所以我通常会选择【参数化SQL】的方法去实现数据库的访问过程,而且会将SQL语句与项目代码(C#)分离开。
不过,有些人可能会说:我的业务逻辑很复杂,Where中的过虑条件不可能事先确定,因此不拼接SQL还不行。
看到这些缺点,ORM用户可能会认为:使用ORM工具就是终极的解决方案。
是的,的确ORM可以解决这些问题。
但是,解决方案并非只有ORM一种,还有些人就是喜欢写SQL呢。
所以,这篇博客不是写给ORM用户的,而是写给所有喜欢写SQL语句的朋友。
CPQuery是什么?
看到博客的标题,你会不会想:CPQuery是什么?
下面是我的回答:
1. CPQuery 是一个缩写:Concat Parameterized Query
2. CPQuery 可以让你继续使用熟悉的拼接方式来写参数化的SQL
3. CPQuery 是我设计的一种解决方案,它可以解决拼接SQL的前二个缺点。
4. CPQuery 也是这个解决方案中核心类型的名称。
希望大家能记住CPQuery这个名字。
CPQuery适合哪些人使用?
答:适合于喜欢手写SQL代码的人,尤其是当需要写动态查询时。
参数化的SQL语句
对于需要动态查询的场景,我认为:拼接SQL或许是必需的,但是,你不要将数值也拼接到SQL语句中嘛,或者说,你应该拼接参数化的SQL来解决你遇到的问题。
说到【拼接参数化SQL】,我想解释一下这个东西了。
这个方法的实现方式是:拼接SQL语句时,不要把参数值拼接到SQL语句中,在SQL语句中使用占位符参数,具体的参数值通过ADO.NET的command.Parameters.Add()传入。现在流行的ORM工具应该都会采用这个方法。
我认为参数化的SQL语句可以解决本文开头所说的那些问题,尤其是前二个。对于代码的维护问题,我的观点是:如果你硬是将SQL与C#混在一起,那么参数化的SQL语句也是没有办法的。如果想解决这个问题,你需要将SQL语句与项目代码分离,然后可以选择以配置文件或者存储过程做为保存那些SLQ语句的容器。
所以,参数化的SQL并不是万能的,代码的可维护性与技术的选择无关,与架构的设计有关。任何优秀的技术都可能写出难以维护的代码来,这就是我的观点。
改造现有的拼接语句
还是说动态查询,假设我有这样一个查询界面:
【图片暂缺】
显然,在设计程序时,不可能知道用户会输入什么样的过滤条件。
因此,喜欢手写SQL的人们通常会这样写查询:
代码如下:
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();
代码如下:
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();
代码如下:
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();
// 将前面的拼接结果绑定到命令对象。
query.BindToCommand(command);
// 输出调试信息。
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");
// 打开连接,执行查询
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;
}
【图片暂缺】
根据前面给出的调试代码:
代码如下:
// 输出调试信息。
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");
代码如下:
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 CPQuery New()
{
return new CPQuery(null, false);
}
public static CPQuery New(bool autoDiscoverParameters)
{
return new CPQuery(null, autoDiscoverParameters);
}
代码如下:
// 下面二行代码是等价的,可根据喜好选择。
var query = "select ProductID, ProductName from Products where (1=1) ".AsCPQuery();
//var query = CPQuery.New() + "select ProductID, ProductName from Products where (1=1) ";
代码如下:
public static CPQuery operator +(CPQuery query, string s)
{
query.AddSqlText(s);
return query;
}
代码如下:
static string ExecuteQuery(CPQuery query)
{
StringBuilder sb = new StringBuilder();
using( SqlConnection connection = new SqlConnection(ConnectionString) ) {
SqlCommand command = connection.CreateCommand();
// 将前面的拼接结果绑定到命令对象。
query.BindToCommand(command);
代码如下:
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 // 字符串参数的处理进度
{
NotSet, // 没开始或者已完成一次字符串参数的拼接。
EndWith, // 拼接时遇到一个单引号结束
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] == '\'' ) { // 遇到一个单引号结束
_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 ) {
// 此时的s应该是字符串参数,不是SQL语句的一部分
// _step 在AddParameter方法中统一修改,防止中途拼接非字符串数据。
this.AddParameter(s.AsQueryParameter());
}
else {
if( s[0] != '\'' )
throw new ArgumentException("正在等待以单引号开始的字符串,但参数不符合预期格式。");
// 找到单引号的闭合输入。
_sb.Append(s.Substring(1));
_step = SPStep.NotSet;
}
}
else {
// 不检查单引号结尾的情况,此时认为一定是SQL语句的一部分。
_sb.Append(s);
}
}
private void AddParameter(QueryParameter p)
{
if( _autoDiscoverParameters && _step == SPStep.Skip )
throw new InvalidOperationException("正在等待以单引号开始的字符串,此时不允许再拼接其它参数。");
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);
}
// 其它需要支持的隐式类型转换操作符重载请自行添加。
}
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);
}
}
}
代码如下:
// 20,可以是byte, short, int, long, uint, ulong ...
// 23.45,可以是float, double, decimal
// 其它类型全部放弃尝试。
代码如下:
static CPQuery BuildDynamicQuery(Product p)
{
// 下面二行代码是等价的,可根据喜好选择。
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; // 整数参数。
if( string.IsNullOrEmpty(p.ProductName) == false )
// 给查询添加一个字符串参数。
query = query + " and ProductName like " + p.ProductName.AsQueryParameter();
if( p.CategoryID > 0 )
query = query + " and CategoryID = " + p.CategoryID; // 整数参数。
if( string.IsNullOrEmpty(p.Unit) == false )
query = query + " and Unit = " + (QueryParameter)p.Unit; // 字符串参数
if( p.UnitPrice > 0 )
query = query + " and UnitPrice >= " + p.UnitPrice; // decimal参数。
if( p.Quantity > 0 )
query = query + " and Quantity >= " + p.Quantity; // 整数参数。
return query;
}
【图片暂缺】
为了方便的使用CPQuery,ClownFish的DbHelper类为所有的数据库访问方法提供了对应的重载方法:
代码如下:
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);
本文来自:http://www.q1010.com/179/7822-0.html
注:关于SQL Server CPQuery 解决拼接SQL的新方法的内容就先介绍到这里,更多相关文章的可以留意四海网的其他信息。
关键词:SQL SERVER
四海网收集整理一些常用的php代码,JS代码,数据库mysql等技术文章。