CPQuery 解决拼接SQL的新方法
来源:asp之家 发布时间:2012-11-30 20:01:46
我一直都不喜欢在访问数据库时采用拼接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");
以及图片反映的事实,可以得出结论:改造后的查询已经是参数化的查询了!


猜你喜欢
- 目录一、慢在哪?二、是否查询了不需要的数据1. 查询不需要的记录2. 多表关联时返回全部列3. 总是查询出全部列4. 重复查询相同的数据三、
- PHP _construct() 函数实例函数创建一个新的 SimpleXMLElement 对象,然后输出 body 节点的内容:<
- 本文实例讲述了Python操作MySQL数据库的两种方式。分享给大家供大家参考,具体如下:第一种 使用pymysql代码如下:import
- 一、基础内容import tkinter as tkfrom PIL import Image,ImageTkdef my():  
- 建议先看vue瀑布流组件上拉加载更多再来食用本文,如果直接想看源码文末就是~文末新增组件优化,之所以没有删优化前的代码是想让以后自己还能看到
- 代码共享url: http://code.google.com/p/region-select-js/ 数据已经更新到中国统计局网站中的20
- 使用base64还原由图片加密而成的字符串。Raw字符串:iVBORw0KGgoAAAANSUhEUgAAAtoAAALaCAYAAAAP7
- ping的原理是发送一个ICMP请求包,然后根据目的地址的应答包来判断是否能够和这个主机进行通信。我们使用python实现,借助于scapy
- MySQL 中,可以为某张表指定多个索引,但在语句具体执行时,选用哪个索引是由 MySQL 中执行器确定的。那么执行器选择索引的原则是什么,
- 什么是seleniumselenium 是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样
- 在安装mha4mysql时,大概步骤是:解压,perl Makefile.PL,make, make install。在执行 perl Ma
- 关于matplotlib如何设置图例的位置?如何将图例放在图外?以及如何在一幅图有多个子图的情况下,删除重复的图例?我用一个简单的例子说明一
- 简介Python Fire是谷歌开源的一个第三方库,用于从任何Python对象自动生成命令行接口(CLI),可用于如快速拓展成命令行等形式。
- 一、identity的基本用法1.含义identity表示该字段的值会自动更新,不需要我们维护,通常情况下我们不可以直接给identity修
- dotnet run 介绍dotnet 相关命令是属于 .NET Core command-line (CLI) 的一部分,Microsof
- 获取Tensor的维数>>> import tensorflow as tf>>> tf.__versi
- 前言在做Vue管理系统的时候,都会遇到的一个需求:每个用户的权限是不一样的,那么他可以访问的页面(路由),可以操作的菜单选项是不一样的,如果
- Create trigger tri_wk_CSVHead_History on wk_CSVHead_History --声明一个tri_
- Matlab常用的输出命令1、disp方法(1)方法(2)方法(3)需要注意:直接加数字不会显示数字,num2str()使数值转换为字符串类
- 使用Resample函数转换时间序列 一、什么是resample函数?它是Python数据分析库Pandas的方法函数。它主要用于