在我们的开发过程中,尤其是在管理系统的开发中,经常会遇到多条件查询(或不定条件查询)的情况,也就是为User输入提供的查询条件有很多不同的查询字段,在实际使用中User会使用哪些条件作为搜索条件并不确定。下图是我们实际项目中一个查询页面的截图。
在实际操作中,用户可以只根据【扣款数】进行查询,所以只要在【扣款数】字段输入该数,其他字段留空,查询语句只会卡上【扣款数】条件,也可以直接根据最近范围进行查询,只要输入开始日期即可。当然,我们在实际开发中无法预测User的行为,所以在正常情况下,我们总是使用Sql拼接的方法来解决这个问题:复制代码如下: string builder sb Sql=new string builder();sbSql。追加('从V_view1中选择*其中1=1 ');/*”注意,为了保证拼接后的Sql语句语法正确,这里应该加上“1=1”,因为它后面的所有查询条件可能都是空的,这个语句应该以‘其中1=1’结尾。之前在花园里看到一篇文章说加“1=1”对查询效率有一定影响,还没有深入研究,对此我有所保留。由于这里只针对一般开发,数据量不是很大,暂时不讨论这个问题*/if(!字符串。IsNullorEmpty(varGRNO)) sbSql。AppendFormat('和BOLNR='{0} ',VarGrnO);这样,在生成Sql语句之前就判断了用户的输入行为:对于某个查询条件,如果用户有输入,则添加到Sql的Where条件中,如果没有输入,则不考虑。
对于日期范围的判断,可以这样写:复制代码代码如下: StringSulier SbSQL=new StringSulier();sbSql .追加('从V_view1中选择*其中1=1 ');if(!字符串IsNullorEmpty(varGRNO)) sbSql .' AppendFormat('和BOLNR=“{ 0 }”,VarGrnO);if(!字符串IsNullorEmpty(VardtFrOm)){ SbSQL .' AppendFormat('和CRDate=“{ 0 }”),转换.今日时间(VardtFrom));if(!字符串IsNullorEmpty(VardTo)){ SbSQL .' AppendFormat('和CRDate lt=“{ 0 }”),转换.今日时间(VardTo));} } 下面是我们实际开发中的完整代码(省略了一些无关的逻辑): 复制代码代码如下:公共DataTable GetGRCollections(字符串瓦尔希普托,字符串varGRNO,字符串varGRNO,字符串varMaterialNO,字符串varPL,字符串varPLto,字符串varCustomerID,字符串varCustomerID1,字符串varCustomerPN,字符串varDateFrom,字符串varDateTo,字符串varChecked,字符串varsupplierierierpn){尝试{ #区域代码在此..DataTable dtResult=new DataTable();StringBuilder SbSQL=new StringBuilder();sbSql .追加(' SELECT * ').追加(' FROM V_QueryGR ').追加(' WHERE(GRTiME=' VarDateFrom ')和GRTiME=' VarDateto ' ')'));if(!字符串IsNullOrEmpty(varShipto)){ SbSqL .追加(' and Plant=' ' varShipto ' ');} if(!字符串IsNullOrEmpty(varGRNO)) { if(!字符串IsNullOrEmpty(varGRNOto)) sbSql .追加('和(GRNO=' ' varGRNO ')和GRNO=' ' VarGRNO ' ')'));else sbSql .追加('和GRNO=' ' Vargno ' ');} if(!字符串IsNullOrEmpty(VarMateriano)){ SbSQL .追加('和材料号=' ' VarMateriano ' ');} if(!字符串IsNullOrEmpty(varPL)) { if(!字符串IsNullOrEmpty(varPLto)) sbSql .追加('和(包装号=' VarPl ')和包装号=' VarPlto ' ')'));else sbSql .追加('和包装号=' ' VarPl ' ');} if(!字符串IsNullOrEmpty(Varccustomerid)){ SbSQL .追加('和CustomID=' ' Varcusterid ' ');}如果(字符串IsNullOrEmpty(Varcustomerid)){ cls mon Objcommon=new cls mon(用户数据);sbSql .在(' ObjCommon)中追加('和CustomID .GetVendorPermissionString()')');} if(!字符串IsNullOrEmpty(Varccustomerid 1)){ SbSQL .追加('和custom id 2=' ' Varcusterid 1 ' ');} if(!字符串IsNullOrEmpty(Varccustomerpn)){ SbSQL .追加('和客户pn=' ' Varcuest pn ' ');} if(!字符串IsNullOrEmpty(varDateFrom)) { if(!字符串IsNullOrEmpty(varDateTo)) sbSql .追加('和(GRTime=' ' varDateFrom ')和GrTiME=' VarDateto ' ')'));else sbSql .追加('和包装号=' ' VarDateFrom ' ');} if(VarChecked==' Checked '){ SbSQL .追加('和check price=1’);} if(VarChecked==' Unchecked '){ SbSQL .追加('和check price=0’);} if(!字符串IsNullOrEmpty(VarSupplierpn)){ SbSQL .追加('和suplierpn=' ' VarSupplierpn ' ');}请尝试{ ControlHandleDB();dtResult=ControlSqlAccess .GetDataTable(sbSql .ToString());}接球{投掷;}最后{控制访问.CloseConnection();}返回dtResult # end region } catch(CommonObjectsException ex){ } catch(Exception ex){ } }这样一来,如果参数多一点的话,一个简单的得到方法就要写50行以上的代码,虽然不能以代码的行数来评定开发效率,但这种方法无疑增加了代码量,也降低的代码的可读性和可维护性。以前,为了给这种情况找到一种更"优雅",更简洁的方法,也有在网上找了一些资料,发现其他人的方法也是大同小异,差不多都是这样按条件拼接。园子里有一位同学(现在忘记是哪位了O(_)O哈!)提出了一种解决方案就是把判断的逻辑直接写到结构化查询语言语句或者存储过程中:复制代码代码如下:从V_view1中选择*其中((ISNULL(@varGRNO ' ')')和[emailprotected])或(1=1))这个方法虽然一定程度上减少了代码量,但是把业务逻辑混杂在结构化查询语言语句中,个人感觉不是太好的方法,而且大大增加了维护的难度。当然,有兴趣的同学可以自己去研究。
既然以上方法都有弊端,有没有更好的解脱方案?答案是肯定的。上次用EF的时候,突然想到里面的扩展方法。Net可以优化这个问题。首先,让我们看看什么是扩展方法,然后让我们解释MSDN:扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或修改原始类型。扩展方法是一种特殊的静态方法,但它可以像扩展类型上的实例方法一样被调用。使用系统。我们常用的Linq中引用的linq其实是一个扩展方法库。有关更多详细信息,请参考MSDN和c#扩展方法。这里我只举一个简单的例子:比如判断一个字符串在正常情况下是否为空,编写如下:复制代码如下:string。is ullrempty(str);如果我们添加一个自己扩展的方法:///summary////检查字符串是否为空(isnullrempty)////summary///param name=' str '/param///returns/returns public static bool IsNullOrEmpty(此字符串为str) { return string。IsNullOrEmpty(字符串);}那么就可以判断以后字符串是否为空:复制代码如下: str。IsNullOrEmpty();是不是简单多了,优雅多了?好了,让我们回到主题,看看如何利用扩展方法的特性优化Sql语句的拼接。既然扩展方法允许我们以实例方法的方式调用静态方法,那么我们是否可以将一个方法扩展到Sql语句的字符串实例来操作它呢?比如这个Sql:复制代码如下: String builder sb SQL=New String builder();sbSql。追加('从V_view1中选择*其中1=1 ');if(!字符串。IsNullorEmpty(varGRNO)) sbSql。AppendFormat('和BOLNR='{0} ',VarGrnO);其实就是判断一个变量,然后操纵字符串实例。
那么,我们就加行一个这样的扩展:复制代码代码如下:公共静态字符串strEquals(此字符串为strSql,字符串为strValue,字符串为ColName) { if(!字符串IsNullOrEmpty(strValue))返回字符串。格式(strSql '和{0}='{1} ',ColName,Strvalue);否则返回strSql}看到没有,在方法内部进行参数的非空判断,那么,上面的代码就可以这样写:复制代码代码如下:字符串' strSql='从V_view1中选择*其中1=1 ' strSql=strSql。stre quals(Vargrino,BOLNR)是不是少了很多代码?如果有更多的参数,我们可以写的想Linq一样优雅:复制代码代码如下:字符串' strSql='从V_view1中选择*其中1=1 '。strEquals(varGRNO,BOLNR).strEquals(varPLNO,VBELN).strEquals(varPONO,EBELN)对于喜欢语句,进行下面的扩展复制代码代码如下:公共静态字符串strLike(此字符串strSql,字符串strValue,字符串ColName) { if(!字符串IsNullOrEmpty(strValue))返回字符串。格式(strSql '和{0},如“%{1}%”、ColName、Strvalue);否则返回strSql}和范围的扩展:复制代码代码如下:公共静态字符串strequal吸附剂吐温(此字符串为strSql、字符串为strStart、字符串为斯特朗,字符串为ColName) { if(字符串.IsNullOrEmpty(strStart)字符串IsNullOrEmpty(StrAnD))返回strSqlelse if(!字符串IsNullOrEmpty(strStart)!字符串IsNullOrEmpty(StrAnD)){ return STrsql。STrbigeger(STrstart,ColName).str maller(StrAnD,ColName);} else if(字符串IsNullOrEmpty(strStart)!字符串IsNullOrEmpty(strendd))返回strSql。strequals(strendd,ColName);其他返回strSql.strEquals(strStart,ColName);} 这样一来,上面一大段的代码就可以写成这样:复制代码代码如下:公共DataTable GetGRCollections(字符串瓦尔希普托,字符串varGRNO,字符串varGRNO,字符串varMaterialNO,字符串varPL,字符串varPLto,字符串varCustomerID,字符串varCustomerID1,字符串varCustomerPN,字符串varDateFrom,字符串varDateTo,字符串varChecked,字符串varsupplierierierpn){尝试{ #区域代码在此..DataTable dtResult=new DataTable();字符串' strSql='从V_QueryGR中选择*其中1=1 '。dtequal吸附剂吐温(varDateFrom,varDateTo,GRTime).压力质量(瓦希普托,工厂)。strequalsabtween(varGRNO,GRNO).应力质量(材料编号,材料编号)。strequal吸附剂吐温(varPL,PackingNO).strEquals(varCustomerID,CustomID).strEquals(varCustomerID1,CustomID2).strEquals(varCustomerPN,CustomerPN).dtequal吸附剂吐温(varDateFrom,varDateTo,GRTime).strEquals(varsupplierierpn,SuplierPN)尝试{ ControlHandleDB();dtResult=ControlSqlAccess .GetDataTable(sbSql .ToString());}接球{投掷;}最后{控制访问.CloseConnection();}返回dtResult # end region } catch(CommonObjectsException ex){ } catch(Exception ex){ } }对于其他的一下扩展方法,我写了一个类文件,有兴趣的可以点此下载。第一次正正经经的写博文,累死我了。由于自己也是个菜鸟,想把一个问题讲清楚让更多的"菜鸟"也能看懂,难免有些啰嗦,有不足的地方还请大家多多指教。