网络编程
位置:首页>> 网络编程>> 数据库>> CPQuery 解决拼接SQL的新方法

CPQuery 解决拼接SQL的新方法

 来源:asp之家 发布时间:2012-11-30 20:01:46 

标签:拼接SQL,CPQuery

我一直都不喜欢在访问数据库时采用拼接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();


如果使用这种方式,本文开头所说的前二个缺点肯定是存在的。
我想很多人应该是知道参数化查询的,最终放弃或许有以下2个原因:
1. 这种拼接SQL语句的方式很简单,非常容易实现。
2. 便于包装自己的API,参数只需要一个(万能的)字符串!
如果你认为这2个原因很难解决的话,那我今天就给你 “一种改动极小却可以解决上面二个缺点”的解决方案,改造后的代码如下:

代码如下:


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();


你看到差别了吗?
差别在于第一行代码,后面调用了一个扩展方法:AsCPQuery(true) ,这个方法的实现代码我后面再说。
这个示例的主要关键代码如下:

代码如下:


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");


以及图片反映的事实,可以得出结论:改造后的查询已经是参数化的查询了!

0
投稿

猜你喜欢

  • 用ASP编写网站应用程序时间长了,难免会遇到各式各样的问题,其中关于如何上传文件到服务器恐怕是遇见最多的问题了,尤其是上传图片,比如你想要在
  • 思考一个问题:怎么实现在第一次检索的基础上进行二次检索?通常,我们的做法是第一次检索时保存检索条件,在第二次行检索时组合两次检索条件对数据库
  • 如何显示数据库里的图片?asp调用数据库中的图片并显示。怎样把数据库里的图片显示出来?我们以gif格式的图片为例,代码如下:showimag
  • 原型:EventManager是一个重要的原型,它用来赋予对象自定义事件的能力当对象类型的原型继承EventManager时,对象具有定义、
  • parent.html  中的代码为:<iframe marginwidth="0"
  • 这篇是Nicholas讨论如果防止脚本失控的第二篇,主要讨论了如何重构嵌套循环、递归,以及那些在函数内部同时执行很多子操作的函数。基本的思想
  • // 格式化字符串 Fmt("{0}.[{id}].{name}",{id:1,name:'
  • 4款JavaScript放大镜特效脚本。准确的说,Anythingzoomer和Bezoom才是正宗的放大镜特效,当鼠标悬浮在图片上时,能放
  • 在 asp 应用中,经常用到 Session 对象来保存用户临时私有数据,而 asp 的 Session 对象是依赖于浏览器的 Cookie
  • 事件流浏览器中的事件流意味着页面上可有不仅一个,甚至多个元素响应同一个事件。而这一个或多个元素响应事件发生的先后顺序在各个浏览器(主要针对I
  • 在这个擦亮自己的眼睛去看SQL Server的系列中的第二篇中提过要写历史渊源,这里的历史主要描述的是数据库本身的历史与SQL Server
  • 1069错误(由于登录失败而无法启动服务)解决方法在本版面出现这个问题的频率也算是很高的了,新手通常会比较多遇到这个问题原因很简单,安装SQ
  • 遇到一个很实际的问题:由于不想增加目录的深度,减少磁盘寻址的时间,需要减少一些目录层级。大家都知道建立文件夹是为了让文件管理更加的方便,现在
  • Apple4.us的张亮问我:“很多人说用户体验是苹果的核心竞争力。在用户体验方面,我究竟该从苹果的设计中学习什么” ?关于这个问题的答案我
  • 1.不要放过任何一个看上去很简单的小编程问题——他们往往并不那么简单,或者可以引伸出很多知识点;2.会用asp,并不说明你会asp;3.看a
  • 今天做站时碰到个小问题:ASP正则获取文章内容图片地址,现在将此方法的思路拿出来分享下:Function RegExp_Execu
  • 一:直接把MDB(MDE)文件放到网络中的共享目录中,在客户端做好对应的快捷方式二:数据库折分(菜单:工具,实用工具,折分)成前后台,把后台
  • 一、垃圾还是经典网页技术更新很快,一个网站的界面设计寿命仅仅2-3年而已。不管是垃圾还是精品,都没有所谓的经典。经典只存在于是哪个首次成功创
  • 好不容易有些空余时间,便拿来写自己的CSS选择器引擎了,这个CSS选择器引擎的目标只有三个:速度要快代码要精简要支持CSS3的选择器。希望通
  • Wingdings字体,Symbol字体<html>  <head>  <title>
手机版 网络编程 asp之家 www.aspxhome.com