Ну, не похоже, что нативные шаблоны T4 справляются с этим так хорошо. В дни VS2003 мы действительно создали что-то более сложное, поэтому я обновил его для Subsonic. Идея заключалась в том, что каждый хранимый процесс получает класс вместо функции и имеет нулевые свойства для параметров. Я изменил несколько шаблонов, я уверен, что что-то здесь не хватает, и это не «качество производства», но вот StoredProcedures.tt
<#@ template language="C#v3.5" debug="False" hostspecific="True" #>
<#@ output extension=".cs" #>
<#@ include file="SQLServer.ttinclude" #>
<#
var sps = GetSPs();
if(sps.Count>0){
#>
using System;
using SubSonic;
using SubSonic.Schema;
using SubSonic.DataProviders;
using System.Data;
namespace <#=Namespace#>{
public partial class <#=DatabaseName#>DB{
<# foreach(var sp in sps){#>
public class <#=sp.CleanName#> : StoredProcedure
{
public <#=sp.CleanName#>(<#=DatabaseName#>DB database, <#=sp.ArgList#>) : base("<#=sp.Name#>",database.Provider)
{
<# foreach(var par in sp.Parameters){
if (par.IsOutput) { #>
Command.AddOutputParameter("<#=par.Name#>",DbType.<#=par.DbType#>);
<#
}
else
{
#>
Command.AddParameter("<#=par.Name#>",<#=par.CleanName#>,DbType.<#=par.DbType#>);
<#
}
}#>
}
<# foreach (var par in sp.Parameters) { #>
public <#= par.SysType #><#= par.ShouldBeNullable ? "?" : "" #> <#= par.CleanName #>
{
get
{
object val = Command.Parameters.GetParameter("<#= par.Name #>").ParameterValue;
return val == DBNull.Value ? default(<#= par.SysType #><#= par.ShouldBeNullable ? "?" : "" #>) : (<#= par.SysType #><#= par.ShouldBeNullable ? "?" : "" #>)val;
}
set
{
<# if (par.ShouldBeNullable) { #>
object val = value.HasValue ? (object)value : (object)DBNull.Value;
Command.Parameters.GetParameter("<#= par.Name #>").ParameterValue = val;
<# } #>
}
}
<# } #>
}
<# } #>
}
}
<# }#>
А вот функция GetSPParams из SQLServer.ttinclude
List<SPParam> GetSPParams(string spName){
var result=new List<SPParam>();
string[] restrictions = new string[4] { DatabaseName, null, spName, null };
using(SqlConnection conn=new SqlConnection(ConnectionString)){
conn.Open();
var sprocs=conn.GetSchema("ProcedureParameters", restrictions);
conn.Close();
foreach(DataRow row in sprocs.Select("", "ORDINAL_POSITION")){
SPParam p=new SPParam();
p.SysType=GetSysType(row["DATA_TYPE"].ToString());
p.DbType=GetDbType(row["DATA_TYPE"].ToString()).ToString();
p.Name=row["PARAMETER_NAME"].ToString().Replace("@","");
p.IsOutput=(row["PARAMETER_MODE"].ToString() == "INOUT");
p.CleanName=CleanUp(p.Name);
result.Add(p);
}
}
return result;
}
А затем объявление класса из Settings.ttinclude
public class SP{
public string Name;
public string CleanName;
public string ClassName;
public List<SPParam> Parameters;
public SP(){
Parameters=new List<SPParam>();
}
public string ArgList{
get{
StringBuilder sb=new StringBuilder();
foreach(var par in Parameters){
if (par.ShouldBeNullable) { continue; }
if(sb.Length != 0)
{
sb.Append(", ");
}
sb.AppendFormat("{0}{1} {2}", par.SysType, par.ShouldBeNullable ? "?" : "", par.CleanName);
}
return sb.ToString();
}
}
}
public class SPParam{
public string Name;
public string CleanName;
public string SysType;
public string DbType;
public bool IsOutput;
public bool ShouldBeNullable
{
get
{
return IsOutput && (SysType == "int" || SysType == "bool" || SysType == "double" || SysType == "long" || SysType == "short" || SysType == "decimal");
}
}
}