selectquerybuilder类允许在你的代码中建立复杂的sql语句和命令。它也能帮助于避免sql注入式攻击。
介绍
承认,并且我们都这样作过,也认为下面的方式是最好的和唯一的方式。就是我们建立大量的字符串包含所有的where子句,然后提交到数据库去执行它。来断的加语句到我们的sql字符串,极有可能会带来bugs和sql注入式攻击的危险。并且也使得我们的代码更难看也不易于管理。
这种情况必须停止,但如何停止?有人说使用存储过程。但它并没有真正的解决这个问题。你还得动态建立你的sql语句,只不过有问题移到数据库层面上了,依然有sql注入的危险。除了这个“解决方案”外,可能还有非常多的选择供你考虑,但它们都会带来一个基本的挑战:让sql语句工作的更好、更安全。
当我从我的在线dal(数据访问层)生成工具http://www.code-engine.com/建立c#模板时,我想提供一个易于使用的方法来定制查询数据。我不再想使用“字符串查询”(我以前开发的模板)来查询数据。我厌烦这种凌乱的方式来得到数据。我想用一种清晰的、直觉的、灵活的、简单的方式从表中选择数据,联接一些别的语句,使用大量的where子句,用一些列来分组数据,返回前x个记录。
我开始开发所想的有这种严密功能的selectquerybuilder类。它暴露了许多属性和方法,你能很容易地在select语句中使用它们。一旦调用buildquery()和buildcommand()方法,它能提供一种更好的旧的“字符串查询“或可以使用命令参数的dbcommand对象来查询数据。
使用代码
旧的方式的代码
下面的代码阐明了以前建立select语句的方法,它使用许多类变量来说明应该使用那种连接操作(where,或者or),同时也给你的数据库带来了可能的sql注入式攻击。
string statement = "select top " + maxrecords + " * from customers ";
string whereconcatenator = "where ";
if (companynametextbox.text.length > 0)
{
statement += whereconcatenator;
statement += "companyname like '" + companynametextbox.text + "%' ";
whereconcatenator = "and ";
}
if (citytextbox.text.length > 0)
{
statement += whereconcatenator;
statement += "city like '" + citytextbox.text + "%' ";
whereconcatenator = "and ";
}
if (countrycombobox.selecteditem != null)
{
statement += whereconcatenator;
statement += "country = '" + countrycombobox.selecteditem + "' ";
whereconcatenator = "and ";
}
我相信上面的代码对你来说是非常熟悉的,你可能在过去的十多年一直是这样使用的,或者你曾经编码过数据库驱动的搜索功能。让我告诉你这种思想:这种查询你的数据库的方法不能再使用了,它是难看的也是不安全的。
selectquerybuilder方式的代码
同样的查询能够使用selectquerybuilder类建立。
selectquerybuilder query = new selectquerybuilder();
query.selectfromtable("customers");
query.selectallcolumns();
query.toprecords = maxrecords;
if (companynametextbox.text.length > 0)
query.addwhere("companyname", comparison.like,companynametextbox.text + "%");
if (citytextbox.text.length > 0)
query.addwhere("city", comparison.like,
citytextbox.text + "%");
if (countrycombobox.selecteditem != null)
query.addwhere("country", comparison.equals,
countrycombobox.selecteditem);
string statement = query.buildquery();
// or, have a dbcommand object built
// for even more safety against sql injection attacks:
query.setdbproviderfactory(
dbproviderfactories.getfactory(
"system.data.sqlclient"));
dbcommand command = query.buildcommand();
你能看到,这种方式比直接使用连接字符串更直观。考虑到第一个例子sql注入的危险,通过selectquerybuilder建立的select查询是非常安全的,并不用担心使用的textboxs中的内容。事实上它也非常简单!
使用sql函数
如果你想在你的查询中使用sql函数,你能使用sqlliteral类来打包函数的调用。说明这个类能作什么的最好方式就是给你显示一小段代码例子:
selectquerybuilder query = new selectquerybuilder();
query.selectfromtable("orders");
query.addwhere("orderdate", comparison.lessorequals,new sqlliteral("getdate()"));
如果我们没有打包getdate()函数调用到sqlliteral类中,建立的查询就会产生where子句:orderdate<=’getdate()’。当然,我们希望在语句中的这个函数没有被单引号包围。这时sqlliteral就可以派上用场了:它直接拷贝字符串到输出,并没有把它格式化成字符串。现在的输出where子句应当是orderdate<=getdate()!
查询中使用joins
要创建到其它表的joins,你能使用addjoin方法。下面的代码显示了如何创建一个从ordres表到customers表的inner join。
selectquerybuilder query = new selectquerybuilder();
query.selectfromtable("orders");
query.addjoin(jointype.innerjoin,"customers", "customerid",comparison.equals,"orders", "customerid");
query.addwhere("customers.city",comparison.equals, "london");
这段代码选择所有居住在london的客户的订单。一旦调用了buildquery方法,就会产生下面的sql语句:
select orders.*
from orders
inner join customers on orders.customerid = customers.customerid
where (customers.city = 'london')
注意到缺省的查询只会建立所选择的表的selects * 语句(这个例子中的orders.*)。如果你也想选择连接表的列的话,你必须得显式地选择它们。你能通用调用query.selectcolumns(“orders.*”,”customers.*”)。 建立计算查询
如果你想对你的数据库执行一个计算查询。你能使用selectcount方法如同下面显示的:
query.selectcount();
在更加复杂的计算查询中,你可能想使用group by语句。看一下下面的例子,它显示了如何使用groupby和addhaving方法。
selectquerybuilder query = new selectquerybuilder();
query.selectcolumns("count(*) as count", "shipcity");
query.selectfromtable("orders");
query.groupby("shipcity");
query.addhaving("shipcity", comparison.notequals, "amsterdam");
query.addorderby("count(*)", sorting.descending);
上面的代码选择了每个城市的订单数,并用订单数目排序,不考虑来自制amsterdam的订单,buildquery方法的输出结果应当是:
select count(*) as count, shipcity
from orders
group by shipcity
having (shipcity <> 'amsterdam')
order by count(*) desc
复杂的where语句
如果你曾经用过微软的access或sql server的内置的查询生成器的话,是否惊讶你能建立和代码一样的包含多层ands和ors,并没有关心()符号的位置的查询?是的?我也能!
你能使用selectquerybuilder类实现!你能加多层的where语句到你的查询。缺省,所有对query.addwhere的调用被放在查询的第一层上。你可以把它比作sql server查询生成器的’criteria’列;第二、三、四层等相应地对应于’or…’列。
看一下下面的sql server查询生成器的快照,通过它我能快速地把简单的假的select语句放在一起来:
如你看到的,我创建一个查询,它选择所有在1-1-2005日期之前的客户’vinet’的订单,和所有30-6-2004日期之前或1-1-2006日期之后的客户’tomsp’的订单(请不要问为什么有人想查询某个人的订单,这仅仅是一个 例子)。这个查询能够建立:
selectquerybuilder query = new selectquerybuilder();
query.selectfromtable("orders");
// add 'criteria' column to level 1
query.addwhere("customerid", comparison.equals,"vinet", 1);
query.addwhere("orderdate", comparison.lessthan,new datetime(2005,1,1), 1);
// add first 'or...' column to level 2
query.addwhere("customerid", comparison.equals, "tomsp", 2);
query.addwhere("orderdate", comparison.lessthan,new datetime(2004,6,30), 2);
// add second 'or...' column to level 3
query.addwhere("customerid", comparison.equals,"tomsp", 3);
query.addwhere("orderdate", comparison.greaterthan,new datetime(2006,1,1), 3);
当调用 buildquery时,所有定义的层将被or到一起,几乎和sql server生成的一样。
如果你到所产生的语句接近一样时,想让查询更复杂,你可能会说“我的放两个随后的语句一起放在一个语句中,在两个日期间使用or”。你能够这样作。在sql server查询生成器中,这个查询看起来像:
同样,它也可能使用selectquerybuilder通过创建’嵌套的where子句’来实现。
selectquerybuilder query = new selectquerybuilder();
query.selectfromtable("orders");
// add 'criteria' column to level 1
query.addwhere("customerid", comparison.equals, "vinet", 1);
query.addwhere("orderdate", comparison.lessthan,
new datetime(2005,1,1), 1);
// add 'or...' column to level 2
query.addwhere("customerid",
comparison.equals, "tomsp", 2);
// add the date selection clause
whereclause clause =query.addwhere("orderdate", comparison.lessthan,
new datetime(2004,6,30), 2);
// add a nested clause to the captured clause
clause.addclause(logicoperator.or,
comparison.greaterthan, new datetime(2006,1,1));
注意到我用了一个whereclause对象,它由addwhere调用返回。接着调用clause.addclause创建一个嵌套的子句柄,并且选择通过指定logicoperator.or来把它or到第一个子句上。所产生的语句如下:
select orders.*
from orders
where
(
(customerid = 'vinet')
and (orderdate < '2005/01/01 12:00:00')
)
or
(
(customerid = 'tomsp')
and (orderdate < '2004/06/30 12:00:00' or
orderdate > '2006/01/01 12:00:00')
)
请注意这个例子中日期包含’12:00:00’,这是因为我在datetime构造体中忽略了时间。但这只要由于我的习惯。如果我使用new datetime(2006,1,1,0,0,0),日期字符串将包含’00:00:00’。
结论
在介绍中我就提到,selectquerybuilder是codeengine框架的一部分。这个框架 也包含了deletequerybuilder,updatequerybuilder,insertquerybuilder。我在通过我的c#dal产生器生成的代码中使用这些生成器。你能从www.code-engine.com上下载一份 框架dll的拷贝。在这期间我也将发布其它的查询生成器的源代码。同时如果你有什么问题,评价或建议,请及时与我联系。本文来源于网页设计爱好者web开发社区http://www.html.org.cn收集整理,欢迎访问。