对 NetTiers 代码生成器模板的修改 <br>使生成的搜索存储过程更灵活






3.94/5 (9投票s)
2006年10月25日
5分钟阅读

45890

290
对 nettiers 代码生成器模板的修改 <br>使生成的搜索存储过程更强大(支持 Transact-sql 的 like 操作符),并更智能地搜索 datetime/smalldatetime 数据类型的列
引言
本文的代码是对 nettiers 代码模板的一个非常小的修改,该模板与著名的 Code Smith 代码生成器一起用于自动生成 SQL Server 的数据访问层(创建存储过程)。此修改增加了生成的搜索/查找存储过程的功能,解决了支持“like”操作符的问题,并使该过程能够更智能地搜索 datetime/smalldatetime 数据类型的列。
背景(可选)
我假设您了解 C#、Visual Studio .Net 2005、Sql Server 2005、Transact-sql、Code Smith 代码生成器,并经历过 nettiers 为您生成的搜索/查找存储过程存在的问题。
使用代码
要使用修改后的 nettiers 版本,请执行以下操作- 下载与本文相关的 zip 文件,然后解压。您会发现 nettiers 代码生成器模板中有两个文件夹,其中包含修改后的文件。
- 将这些文件复制到您计算机上 nettiers 根文件夹中的相同文件夹。
- 使用 Code Smith 代码生成器程序编译 nettiers。
- 最后,使用 nettiers 为您生成数据访问层。
就这样……!!现在,您可以享受 Find/Search 存储过程,而无需对其进行任何修改即可支持 Transact-sql 世界中的“like”操作符,还可以智能地搜索 datetime/smalldatetime 数据类型的列……!!
代码背后的思路
作为代码的介绍,我必须说明 nettiers 在 `StoredProceduresXml.cst` 文件中构造包含数据访问层存储过程创建的 SQL 查询文件,该文件位于 nettiers 模板根目录的 `DataAccessLayer.SqlClient` 文件夹下。这是通过一个名为 `CommonSqlCode` 的 C# 辅助类完成的,该类继承自 Code Smith 引擎的 `CodeTemplate` 类。`CommonSqlCode` 类实现在 `CommonSqlCode.cs` 文件中,该文件位于 nettiers 计算机根目录的 `TemplateLib` 文件夹下。所以您可以说,对任何过程的任何修改,nettiers 都会在这些文件周围生成。现在,为了使生成的 Find/Search 存储过程满足支持 like 操作符的第一个要求,它已被修改为从过程的构造参数中构建一个大的 SQL 语句,然后使用 SQL Server 自带的 `sp_executesql` 内部存储过程来运行它。
对于每个可查找的列(nvarchar、varchar 等),nettiers 会创建一个搜索参数,在修改后的版本中会检查该参数的值是否包含“%”,这意味着使用 like 操作符而不是 = 操作符……!!
以下是使用我的修改版本生成的 Find/Search 过程的一部分,它满足了第一个要求。
注意:这里的代码注释不是 SQL 注释,而是对代码的解释(生成器不会生成它们)
-- this line generated once on top of the procedure -- it is the big sql statement holder variable declare @sql nvarchar(max) --checks the paramter value if it has the '%' that means the query wanna use the like operator <% for (int i = 0; i < cols.Count; i++) { if(cols[i].NativeType == "datetime" || cols[i].NativeType == "smalldatetime"){ Response.Write("\t\t\t\t" + GetSqlParameterXmlNodeForDateTimeColumns(cols[i], cols[i].Name, false, true) + Environment.NewLine); }else{ Response.Write("\t\t\t\t" + GetSqlParameterXmlNode(cols[i], cols[i].Name, false, true) + Environment.NewLine); } } %><![CDATA[ <%= GetSetTransactionIsolationLevelStatement() %> DECLARE @SQL NVARCHAR(MAX) IF ISNULL(@SearchUsingOR, 0) <>if(charindex('%',ParameterName)>=0) begin set @sql = @sql + 'ParameterName like ' + ParameterValue end else begin @sql = @sql + 'ParameterName = ' + ParameterValue end -- this lines generated once at the buttom of the stored procedure exec sp_executesql @sql select @@rowcount
对于搜索 datetime/smalldatetime 数据类型列更智能的第二个要求,由我的修改版 nettiers 生成的关于 datetime/smalldatetime 数据类型列的最终存储过程代码可能如下所示
--for each column of datatype datetime or smalldatetime two input paramters generated on top of the stored procedure -- the type of those paramters is nvarchar cause i will concatinate it to the @sql statement @columnName_From nvarchar (30) = null, @columnName_To nvarchar (30) = null -- the [From Date] Parameter Supplied and the [To Date]Parameter not supplied(=null) -- means find recored after or equal to that date if(@columnName_From is not null AND @columnName_To is null) Begin SET @sql = @sql + 'AND([columnName] >=''' + @columnName_From + ''')' End -- the [From Date] Parameter not Supplied (=null) and [To Date] Parameter Supplied means -- find recoreds before or equal to that date if(@columnName_From is null AND @columnName_To is not null) Begin SET @sql = @sql + 'AND([columnName] <=''' + @columnName_To +''')' End -- both paramters supplied searching within a period starts from the [From Date] to the [To Date] if(@columnName_From is not null AND @columnName_To is not null) -- the From Date Paramter Supplied means find recored after or equal to that date Begin SET @sql = @sql + 'AND([columnName] >=''' + @columnName_From + ''' AND [columnName] <=''' + @columnName_To + ''')' End -- this lines generated once at the buttom of the stored procedure exec sp_executesql @sql select @@rowcount --to have results of exact date u have to supply the same value for the two paramters我亲爱的读者会注意到连接到 `@sql` 变量的语句前面有一个 AND 操作符……事实上,nettiers 会生成一个名为 `@SearchUsingOR` 的智能参数,它控制搜索结果的限制,这意味着使用 AND 或使用 OR 来构造存储过程。当然,修改后的版本也有这个功能,但我只提供生成的存储过程的这部分代码以求简洁。您可以查看使用修改后的 nettiers 生成的 Find/Search 过程,以了解修改后的模板将做什么的整体情况。
注意:要搜索精确日期,您只需为两个参数提供相同的值……!!!!
代码修改
StoredProcedureXml.cst 后置代码修改
- Find/Search 存储过程的策略已更改为使用 `sp_executesql` 内部存储过程,这要求对 nettiers 原始版本中某行之后的代码进行大量修改。
if(IncludeFind){%>
- 已添加对 datetime/smalldatetime 列的检查,以确定如何构造过程的参数列表。
这是因为正如我告诉您的关于满足第二个要求一样,对于任何 datetime/smalldatetime 数据类型的列,我们需要两个参数而不是像其他可查找数据类型列那样只需要一个。
代码如下所示
if(cols[i].NativeType == "datetime" || cols[i].NativeType == "smalldatetime"){ Response.Write("\t\t\t\t" + GetSqlParameterXmlNodeForDateTimeColumns(cols[i], cols[i].Name, false, true) + Environment.NewLine); }else{ Response.Write("\t\t\t\t" + GetSqlParameterXmlNode(cols[i], cols[i].Name, false, true) + Environment.NewLine); }
StoredProcedureXml.cst 文件中使用的任何方法都在 CommonSqlCode.cs 文件中实现。
上面代码中使用的 `GetSqlParameterXmlNodeForDateTimeColumns` 是修改后的 CommonSqlCode.cs 文件中 `GetSqlParameterXmlNode` 方法的新重载……!!
CommonSqlCode.cs 修改
* 添加了一个名为 `GetSqlParameterXmlNodeForDateTimeColumns` 的新方法,用于生成每个 datetime 数据类型列所需的等于参数的行。
/// Get a SqlParameter statements for The DateTime AND smalldatetime DataType column /// </summary> /// <param name= column >Column for which to get the Sql parameter statements</param> /// <param name= parameterName >the name of the parameter?</param> /// <param name= isOutput >indicates the direction</param> /// <param name = nullDefaults >indicates whether to give each parameter a null or empty default. /// (used mainly for Find sp's)</param> /// <returns>the xml Sql Parameter statement</returns> public string GetSqlParameterXmlNodeForDateTimeColumns(ColumnSchema column, string parameterName, bool isOutput, bool nullDefaults) { string formater = "<parameter name=\"@{0}_From\" type=\"{1}\" direction=\"{2}\" size=\"30\" precision=\"1\" scale=\"0\" param=\" 30\" nulldefault=\" {3}\" /> ; formater += Environment.NewLine + <parameter name=\" @{0}_To\" type=\" {1}\" direction=\" {2}\" size=\" 30\" precision=\" 1\" scale=\" 0\" param=\" (30)\" nulldefault=\ "{3}\" /> ; tring nullDefaultValue = ; if (nullDefaults) { nullDefaultValue = null ; } bool isReal = false; if (column.NativeType.ToLower() == "real") // SQL doesn't like precision or scale on Real { isReal = true; } return string.Format(formater, GetPropertyName(parameterName), "nvarchar" , "Input" , nullDefaultValue,GetSqlParameterParam(column)); }
* 添加了另一个名为 `GetFProcedureLineOfColumn` 的新方法,该方法负责为 Find/Search 存储过程的每个输入参数逐行生成过程。
public string GetFProcedureLineOfColumn(ColumnSchema column,string ProcText,string AND_OR){ if(column.NativeType.IndexOf("int") >=0 ){ ProcText += string.Format("\tif(@{0} is not null){1}\tBegin{1}" ,GetPropertyName(column.Name), Environment.NewLine); ProcText += "\t\t\tSET @sql = @sql +" + string.Format("\t'{2}([{0}] = ''' + convert(nvarchar(255),@{0}) + ''')'{1}",GetPropertyName(column.Name), Environment.NewLine,AND_OR)+ Environment.NewLine; ProcText += "\tEnd" + Environment.NewLine; return ProcText; } if(column.NativeType == "uniqueidentifier" ){ ProcText += string.Format("\tif(@{0} is not null){1}\tBegin{1}" ,GetPropertyName(column.Name), Environment.NewLine); ProcText += "\t\t\tSET @sql = @sql +" + string.Format("\t'{2}(Convert(char(255),[{0}]) = ''' + Convert(char(255),@{0}) + ''')'{1}" ,GetPropertyName(column.Name),Environment.NewLine,AND_OR) + Environment.NewLine; ProcText += "\tEnd" + Environment.NewLine; return ProcText; } if(column.NativeType == "xml"){ ProcText += string.Format("\tif(@{0} is not null){1}\tBegin{1}" ,GetPropertyName(column.Name), Environment.NewLine); ProcText += Environment.NewLine; ProcText += "\tif (charindex('%',convert(varchar(max),@" + GetPropertyName(column.Name) + "))>0)" + Environment.NewLine; ProcText += "\t\tBegin" + Environment.NewLine; ProcText += "\t\t\tSET @sql = @sql +" + string.Format("\t'{2}([{1}] Like ''' + convert(nvarchar(max),@{1}) + ''')'{0}", Environment.NewLine, GetPropertyName(column.Name),AND_OR) + Environment.NewLine ; ProcText += "\t\tEnd" + Environment.NewLine; ProcText += "\tElse" + Environment.NewLine; ProcText += "\t\tBegin" + Environment.NewLine; ProcText += "\t\t\tSET @sql = @sql +" + string.Format("\t'{2}([{1}] = ''' + convert(nvarchar(max),@{1}) + ''')'{0}", Environment.NewLine, GetPropertyName(column.Name),AND_OR) + Environment.NewLine; ProcText += "\t\tEnd" + Environment.NewLine; ProcText += "\tEnd" + Environment.NewLine; return ProcText; } //adjust the sql output for the datetime two var probability if(column.NativeType == "datetime" || column.NativeType=="smalldatetime"){ ProcText += string.Format("\tif(@{0}_From is not null AND @{0}_To is null){1}\tBegin{1}" , GetPropertyName(column.Name),Environment.NewLine); ProcText += "\t\t\tSET @sql = @sql +" + string.Format("\t'{2}([{1}] >=''' + @{1}_From + ''')'{0}", Environment.NewLine, GetPropertyName(column.Name),AND_OR) + Environment.NewLine ; ProcText += "\tEnd" + Environment.NewLine; ProcText += string.Format("\t\tif(@{0}_From is null AND @{0}_To is not null){1}\tBegin{1}" , GetPropertyName(column.Name),Environment.NewLine); ProcText += "\t\t\tSET @sql = @sql +" + string.Format("\t'{2}([{1}] <=''' + @{1}_To +''')'{0}", Environment.NewLine, GetPropertyName(column.Name),AND_OR) + Environment.NewLine ; ProcText += "\tEnd" + Environment.NewLine; ProcText += string.Format("\tif(@{0}_From is not null AND @{0}_To is not null){1}\tBegin{1}" , GetPropertyName(column.Name),Environment.NewLine); ProcText += "\t\t\tSET @sql = @sql +" + string.Format("\t'{2}([{1}] >=''' + @{1}_From + ''' AND [{1}] <=''' + @{1}_To + ''')'{0}", Environment.NewLine, GetPropertyName(column.Name),AND_OR) + Environment.NewLine ; ProcText += "\tEnd" + Environment.NewLine; return ProcText; } ProcText += string.Format("\tif(@{0} is not null){1}\tBegin{1}" ,GetPropertyName(column.Name), Environment.NewLine); ProcText += Environment.NewLine; ProcText += "\tif (charindex('%',@" + GetPropertyName(column.Name) + ")>0)" + Environment.NewLine; ProcText += "\t\tBegin" + Environment.NewLine; ProcText += "\t\t\tSET @sql = @sql +" + string.Format("\t'{2}([{1}] Like ''' + @{1} + ''' OR ''' + @{1} + ''' is null)'{0}", Environment.NewLine, GetPropertyName(column.Name),AND_OR) + Environment.NewLine ; ProcText += "\t\tEnd" + Environment.NewLine; ProcText += "\tElse" + Environment.NewLine; ProcText += "\t\tBegin" + Environment.NewLine; ProcText += "\t\t\tSET @sql = @sql +" + string.Format("\t'{2}([{1}] = ''' + @{1} + ''' OR ''' + @{1} + ''' is null)'{0}", Environment.NewLine, GetPropertyName(column.Name),AND_OR) + Environment.NewLine; ProcText += "\t\tEnd" + Environment.NewLine; ProcText += "\tEnd" + Environment.NewLine; return ProcText; }
警告……!!
以这种方式实现的 Find/Search 存储过程会使您的数据库系统暴露于 SQL 注入攻击……您必须注意搜索表单中的用户输入,方法是强制用户从预定义值中选择,或通过检查用户输入并查看其是否会损害构造的 SQL 语句来简单地防御此类攻击……
一种常用技术是替换 (') 为 (''),如果参数值中包含任何……!!
结论
我希望在座的所有人都能找到这篇文章的用处,让您的编程生活更轻松。
历史
修改通知
修改日期:2006年11月1日,星期三
- 已对生成的 C# 代码进行修改,以适应使用 Find/Search 存储过程的函数。
为了使上述代码能与 nettiers 的前端一起工作,必须对 `SqlEntityProviderBase.generated.cst` 模板文件进行修改。该文件已上传到文章的 nettiers 修改文件组合压缩包中。
- nettiers 生成的 find 方法的语法如下所示
DataGrid1.DataSource=DataRepository.YourClassNameProvider.Find("columnName=value AND/OR columnName=value");
如果您正在搜索 datetime 或 small datetime,那么根据文章,您应该知道已经为这种类型的列创建了两个参数,这两个参数的名称是 `yourColumnName_from` 和 `yourColumnName_to`。然后,如果您想在两个 datetime 值之间搜索一个 date 类型列,您只需在查询中键入如下内容:DataGrid1.DataSource=DataRepository.YourClassNameProvider.Find("columnName_from=datetimevalue AND columnName_to=datetimevalue");
如果您想获取具有相同日期的记录,只需在查询中为两个变量分配相同的值即可。