利用IFormattable接口自动参数化Sql语句
提要
string.Format("{0},{1}",a,b)的用法大家都不陌生了,在很多项目中都会发现很多sql语句存在这样拼接的问题,这种做法很多"懒"程序员都很喜欢用,因为实在是非常的方便,但是这种做法会带来各种Sql注入的问题,所以我今天就说说这个问题,怎么才可以既方便又安全?
ps:当然这也是有代价的,代价就是性能,当然今天是忽略这个问题的,很多性能问题在小项目中都不是问题....
一号配角登场
超简版DBHelper,你可以把他理解为从某个ORM中肢解下来的一个关节
大家都是成年人了,没有技术含量的代码我就不加注释了...
public class DBHelper
{
public DBHelper(string connString)
{
ConnectionString = connString;
} public string ConnectionString { get; private set; } public DataSet GetDataSet(string sql)
{
using (var adp = new SqlDataAdapter(sql, ConnectionString))
{
var ds = new DataSet();
adp.Fill(ds);
return ds;
}
}
}
我先举个栗子
int id = ;
string name = "dsa"; DBHelper db = new DBHelper("Data Source=.;Initial Catalog=Test;Integrated Security=True");
string sql = "SELECT id,name FROM test WHERE id > {0} AND name = '{1}'";
sql = string.Format(sql, id, name);
DataSet ds = db.GetDataSet(sql);
Console.WriteLine(ds.Tables[].Rows.Count);
这就是在一些项目经常看到的代码
这个代码问题刚才讲的很清楚了,因为存在Sql注入的问题.如果name参数等于 "' or 1 = 1"或者类似的语句那么会带来意想不到的灾难
你当然可以说我可以事先判断,去掉一些关键字,但你能保证已经考虑所有的情况了吗?好了,今天要讨论的不是怎么判断注入的问题,而是从根本上杜绝注入的可能!
也就是不存在字符串拼接,参数化执行Sql语句!
再来个参数化的栗子
先为DBHelper加一个方法
public DataSet GetDataSet(string sql,params SqlParameter[] args)
{
using (var adp = new SqlDataAdapter(sql, ConnectionString))
{
adp.SelectCommand.Parameters.AddRange(args);
var ds = new DataSet();
adp.Fill(ds);
return ds;
}
}
调用就变成了这样
int id = ;
string name = "dsa"; DBHelper db = new DBHelper("Data Source=.;Initial Catalog=Test;Integrated Security=True");
//string sql = "SELECT id,name FROM test WHERE id > {0} AND name = {1}";
//sql = string.Format(sql, id, name);
//DataSet ds = db.GetDataSet(sql);
string sql = "SELECT id,name FROM test WHERE id > @id AND name = @name";
DataSet ds = db.GetDataSet(sql, new SqlParameter("id", id), new SqlParameter("name", name));
Console.WriteLine(ds.Tables[].Rows.Count);
这样确实可以解决注入的问题,可是调用起来却麻烦了很多,如果参数多的时候简直就是噩梦啊~~
YY ... 你们懂的
先抛开一些杂念,想想自己想要的什么...
其实很简单,我不想每个参数都new SqlParamete()
int id = ;
string name = "dsa"; DBHelper db = new DBHelper("Data Source=.;Initial Catalog=Test;Integrated Security=True");
string sql = "SELECT id,name FROM test WHERE id > {0} AND name = {1}";
DataSet ds = db.GetDataSet(sql, id, name);
Console.WriteLine(ds.Tables[].Rows.Count);
乍看之下很简单嘛~~~~
public DataSet GetDataSet(string sql, params object[] args)
{
using (var adp = new SqlDataAdapter(sql, ConnectionString))
{
for (int i = ; i < args.Length; i++)
{
string name = "p_" + i; //为参数取名 格式 p_0,p_1,...
adp.SelectCommand.Parameters.Add(new SqlParameter(name, args[i]));//加入参数
args[i] = "@" + name; //替换{0}为@p_0
}
adp.SelectCommand.CommandText = string.Format(sql, args);
var ds = new DataSet();
adp.Fill(ds);
return ds;
}
}
嗯...确实是这样的,看下运行结果

如果是这样呢?
int id = ;
string name = "dsa"; DBHelper db = new DBHelper("Data Source=.;Initial Catalog=Test;Integrated Security=True");
string sql = "SELECT id,name FROM test WHERE id > {0} AND name Like {1}";
DataSet ds = db.GetDataSet(sql, id, name);
Console.WriteLine(ds.Tables[].Rows.Count);
里面有一个Like怎么办?所以我有一个更好的方案IFormattable
主角登场
private struct CommandFormatArgs : IFormattable
{
private SqlCommand _Command;
private Object _Value;
//得到SqlCommand和Value格式化的时候用
public CommandFormatArgs(SqlCommand command, object value)
{
_Command = command;
_Value = value;
}
//在String.Format时会调用这个方法
public string ToString(string format, IFormatProvider formatProvider)
{
string name = "p_" + Identity.NextString();
_Command.Parameters.Add(new SqlParameter(name, _Value));
if (format != null && format.Contains("@"))
{
return "'" + format.Replace("@", "' + @" + name + " + '") + "'";
}
else
{
return "@" + name;
}
}
}
2号配角:Identity自增序列/唯一断标识
这个对象我设计成一个内部结构,因为他的生存周期非常的短暂,只会在方法内使用,所以结构已经够用了
重新实现GetDataSet
public DataSet GetDataSet(string sql, params object[] args)
{
using (var adp = new SqlDataAdapter(sql, ConnectionString))
{
for (int i = ; i < args.Length; i++)
{
args[i] = new CommandFormatArgs(adp.SelectCommand, args[i]);
}
adp.SelectCommand.CommandText = string.Format(sql, args);
var ds = new DataSet();
adp.Fill(ds);
return ds;
}
}
实现自定义格式化参数
在String.Format这个方法中,系统会调用我们实现IFormattable接口中的方法ToString,并且,如果有额外的参数也会在format参数中体现出来
额外的参数就是指 string.Format("{0:yyyy-MM-dd}",obj)中的yyyy-MM-dd
所以如果是Like,我将他指定了一个规则,如:
int id = ;
string name = "a"; DBHelper db = new DBHelper("Data Source=.;Initial Catalog=Test;Integrated Security=True");
string sql = "SELECT id,name FROM test WHERE id > {0} AND name Like {1:%@%}";
DataSet ds = db.GetDataSet(sql, id, name);
Console.WriteLine(ds.Tables[].Rows.Count);
这个调用的时候%@%会被当作format参数传到ToString(string format, IFormatProvider formatProvider)中
public string ToString(string format, IFormatProvider formatProvider)
{
string name = "p_" + Identity.NextString();
_Command.Parameters.Add(new SqlParameter(name, _Value));
if (format != null && format.Contains("@"))
{
return "'" + format.Replace("@", "' + @" + name + " + '") + "'";
}
else
{
return "@" + name;
}
}
处理完的效果就是这样的

================================================

结束
我这里的结束只是指这篇文章的到这里就结束了
IFormattable的用法当然不仅限于此
写这个也仅仅只是做一个抛砖引玉的作用,其实系统有很多很多很好的接口和为这些接口服务的类和方法
只要我们运用得当,都会为我们带来非常多编码上的好处和编码以外的乐趣
利用IFormattable接口自动参数化Sql语句的更多相关文章
- SQL Server SQL性能优化之--数据库在“简单”参数化模式下,自动参数化SQL带来的问题
数据库参数化的模式 数据库的参数化有两种方式,简单(simple)和强制(forced),默认的参数化默认是“简单”,简单模式下,如果每次发过来的SQL,除非完全一样,否则就重编译它(特殊情况会自动参 ...
- 如何用参数化SQL语句污染你的计划缓存
你的SQL语句的参数化总是个好想法.使用参数化SQL语句你不会污染你的计划缓存——错!!!在这篇文章里我想向你展示下用参数化SQL语句就可以污染你的计划缓存,这是非常简单的! ADO.NET-AddW ...
- 在ADO.NET中使用参数化SQL语句访问不同数据库时的差异
在ADO.NET中经常需要跟各种数据库打交道,在不实用存储过程的情况下,使用参数化SQL语句一定程度上可以防止SQL注入,同时对一些较难赋值的字段(如在SQL Server中Image字段,在Orac ...
- 参数化SQL语句
避免SQL注入的方法有两种:一是所有的SQL语句都存放在存储过程中,这样不但可以避免SQL注入,还能提高一些性能,并且存储过程可以由专门的数据库管理员(DBA)编写和集中管理,不过这种做法有时候针对相 ...
- SQL Server参数化SQL语句中的like和in查询的语法(C#)
sql语句进行 like和in 参数化,按照正常的方式是无法实现的 我们一般的思维是: Like参数化查询:string sqlstmt = "select * from users whe ...
- 加载映射文件几种方式和mapper接口注解执行sql语句
一.加载映射文件几种方式 二.mapper接口注解执行sql语句 就将xml中的sql语句放到注解的括号中就可以,一般只用于简单的sql语句合适:
- 利用反射自动生成SQL语句(仿Linq)
转:http://www.cnblogs.com/the7stroke/archive/2012/04/22/2465597.html using System; using System.Colle ...
- C# 参数化SQL语句中的like和in
在写项目的时候遇到一个问题,sql 语句进行 like in 参数化,按照正常的方式是无法实现的我们一般的思维是: Like 参数:string strSql = "select * fro ...
- 利用tcpdump抓取mysql sql语句
这个脚本是我之前在网上无意间找个一个利用tcpdump 抓包工具获取mysql流量,并通过过滤把sql 语句输入. 脚本不是很长,但是效果很好. #!/bin/bash #this script us ...
随机推荐
- mvc理解篇
java是面向对象的语言. mvc是一种设计模式,就像论文排版,设置好论文的框架,字体,大小,颜色等,然后把论文内容往里填.mvc的出现让代码的层次更加的清晰,业务通过数据流实现. mvc框架的优点如 ...
- ajax基础一
AJAX AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 不是新的编程语言,而是一种使用现有标准的新方法. AJA ...
- Json数据中的特殊字符处理
今天在项目中遇到一个问题,页面上的数据突然显示不出来了,查验后得知是Json数据出现了问题.使用JSON从后台向前台传输数据的时候,当数据本身含有一些特殊字符,会导致JSON数据的解析出错.如果内容中 ...
- 国内及Github优秀开发人员列表
自从入了Android软件开发的行道,解决问题和学习过程中免不了会参考别人的思路,浏览博文和门户网站成了最大的入口.下面这些列表取名为:国内及Github优秀开发人员列表,就是浏览后的成果. 虽然下述 ...
- SVN的服务器端用户权限配置
第一:用户的配置 SVN和apache整合的话,用户可以直接使用htpasswd dav_svn.passwd_file_address USERNAME来配置. 而账户的管理可以用dav_svn.a ...
- nodeJS express框架 中文乱码解决办法
最近在研究javascript 的服务端应用 node,之所以想要研究node,是因为前几个月一直在前端挣扎,从javascript入门到在项目中实际使用javascript,确实感悟颇深.javas ...
- PHP二维数组排序
$arrays 要排序的数组 $sort_key 根据排序的key $sort_order 升序降序 SORT_ASC/SORT_DESC $sort_type 排序key类型 SORT_N ...
- 我与solr(二)--导入mysql数据库
关于solr的搭建详见上一篇的随笔. 步骤1: 在webapps中solrhome下新建一个文件夹名字叫做mynode(名字不固定,可以随便取,但是这个名字在后面的配置中会有所关联.)然后在mynod ...
- 更改Xampp-sql的默认密码-配置appche运行环境
用php编写的web应用程序,需运行在php的web容器中,其中apache server是一个针对php web容器,它是apache下的开源项目.通常要运行一个web程序,我们还需要安装数据库软件 ...
- 如何使用BHO定制你的Internet Explorer浏览器
原文出处:Browser Helper Objects: The Browser the Way You Want It一.简介 有时,你可能需要一个定制版本的浏览器.在这种情况下,你可以自由地把一些 ...