使用 C# 和 System.Data.SqlClient,有没有办法在我实际执行之前检索属于 SQL Server 上的存储过程的参数列表?
我有一个“多环境”场景,其中有同一个数据库模式的多个版本。环境的示例可能是“开发”、“暂存”和“生产”。“开发”将有一个版本的存储过程,而“暂存”将有另一个版本。
我要做的就是在传递一个值并调用存储过程之前验证一个参数是否存在。避免 SqlException 而不必捕获它对我来说是一个加分项。
约书亚
使用 C# 和 System.Data.SqlClient,有没有办法在我实际执行之前检索属于 SQL Server 上的存储过程的参数列表?
我有一个“多环境”场景,其中有同一个数据库模式的多个版本。环境的示例可能是“开发”、“暂存”和“生产”。“开发”将有一个版本的存储过程,而“暂存”将有另一个版本。
我要做的就是在传递一个值并调用存储过程之前验证一个参数是否存在。避免 SqlException 而不必捕获它对我来说是一个加分项。
约书亚
您需要SqlCommandBuilder.DeriveParameters(SqlCommand)方法。请注意,它需要额外的数据库往返,因此它对性能有一定的影响。您应该考虑缓存结果。
一个示例调用:
using (SqlConnection conn = new SqlConnection(CONNSTRING))
using (SqlCommand cmd = new SqlCommand("StoredProc", conn)) {
cmd.CommandType = CommandType.StoredProcedure;
SqlCommandBuilder.DeriveParameters(cmd);
cmd.Parameters["param1"].Value = "12345";
// ....
}
您可以使用 SqlCommandBuilder.DeriveParameters() (请参阅SqlCommandBuilder.DeriveParameters - 获取存储过程的参数信息 - ADO.NET 教程)或者这种方式不那么优雅。
尽管它并不完全符合您的要求,但这里有一些示例代码,它使用 SqlConnection.GetSchema() 方法返回与数据库关联的所有存储过程,然后返回每个存储过程的所有参数名称和类型。下面的示例只是将其加载到变量中。请注意,这还会返回所有“系统”存储过程,这可能是不可取的。
史蒂夫
public void LoadProcedureInfo()
{
SqlConnection connection = new SqlConnection();
ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings["ConnectionString"];
connection.ConnectionString = settings.ConnectionString;
connection.Open();
DataTable procedureDataTable = connection.GetSchema("Procedures");
DataColumn procedureDataColumn = procedureDataTable.Columns["ROUTINE_NAME"];
if (procedureDataColumn != null)
{
foreach (DataRow row in procedureDataTable.Rows)
{
String procedureName = row[procedureDataColumn].ToString();
DataTable parmsDataTable = connection.GetSchema("ProcedureParameters", new string[] { null, null, procedureName });
DataColumn parmNameDataColumn = parmsDataTable.Columns["PARAMETER_NAME"];
DataColumn parmTypeDataColumn = parmsDataTable.Columns["DATA_TYPE"];
foreach (DataRow parmRow in parmsDataTable.Rows)
{
string parmName = parmRow[parmNameDataColumn].ToString();
string parmType = parmRow[parmTypeDataColumn].ToString();
}
}
}
}
SqlCommandBuilder.DeriveParameters(command)
这个声明做了我需要的。
这是我解决此问题的方式的完整代码示例。
Public Sub GetLogEntriesForApplication(ByVal settings As FilterSettings,
Optional ByVal RowGovernor As Integer = -1)
Dim command As New SqlCommand("GetApplicationActions",
New SqlConnection(m_environment.LoggingDatabaseConnectionString))
Dim adapter As New SqlDataAdapter(command)
Using command.Connection
With command
.Connection.Open()
.CommandType = CommandType.StoredProcedure
SqlCommandBuilder.DeriveParameters(command)
With .Parameters
If settings.FilterOnLoggingLevel Then
If .Contains("@loggingLevel") Then
.Item("@loggingLevel").Value = settings.LoggingLevel
End If
End If
If settings.FilterOnApplicationID Then
If .Contains("@applicationID") Then
.Item("@applicationID").Value = settings.ApplicationID
End If
End If
If settings.FilterOnCreatedDate Then
If .Contains("@startDate") Then
.Item("@startDate").Value = settings.CreatedDate.Ticks
End If
End If
If settings.FilterOnEndDate Then
If .Contains("@endDate") Then
.Item("@endDate").Value = settings.EndDate.Ticks
End If
End If
If settings.FilterOnSuccess Then
If .Contains("@success") Then
.Item("@success").Value = settings.Success
End If
End If
If settings.FilterOnProcess Then
If settings.Process > -1 Then
If .Contains("@process") Then
.Item("@process").Value = settings.Process
End If
End If
End If
If RowGovernor > -1 Then
If .Contains("@topRows") Then
.Item("@topRows").Value = RowGovernor
End If
End If
End With
End With
adapter.TableMappings.Clear()
adapter.TableMappings.Add("Table", "ApplicationActions")
adapter.TableMappings.Add("Table1", "Milestones")
LogEntries.Clear()
Milestones.Clear()
adapter.Fill(m_logEntryData)
End Using
End Sub
您可以使用 SqlCommandBuilder 对象,并调用 DeriveParameters 方法。
基本上你需要向它传递一个命令,即设置调用你的存储过程,它会访问数据库以发现参数,并在 SqlCommand 的参数属性中创建适当的参数
编辑:你们太快了!!
Mark 拥有最好的 DeriveParameters 实现。正如他所说,请确保您像本教程中一样进行缓存。
但是,我认为这是解决您最初的数据库存储过程版本控制问题的危险方法。如果您要通过添加或删除参数来更改过程的签名,您应该执行以下操作之一:
恕我直言,依靠 DeriveParameters 来验证您使用的存储过程的版本似乎是错误的工作工具。
所有这些 ADO.NET 解决方案都要求代码库代表您查询数据库的元数据。如果您无论如何都要对性能造成影响,也许您应该编写一些辅助函数来调用
Select count(*) from information_schema.parameters
where ...(proc name =.. param name=...) (pseudo-code)
或者甚至可以根据您返回的参数列表生成您的参数。这种技术适用于多个版本的 MS SQL,有时也适用于其他 ANSI SQL 数据库。
几年以来,我一直在将 DeriveParameters 与 .NET 1.1 和 2.0 一起使用,并且每次都像魅力一样工作。
现在我正在使用 .NET 3.5 处理我的第一个任务,并且发现了一个丑陋的惊喜:DeriveParameters 正在使用 SqlDbType“Variant”创建所有参数,而不是正确的 SqlDbTypes。这会在尝试使用数字参数执行 SP 时创建 SqlException,因为 SQL Server 2005 表示 sql-variant 类型不能隐式转换为 int(或 smallint,或 numeric)值。
我刚刚使用 .NET CF 2.0 和 SQL Server 2000 测试了相同的代码,并按预期工作,为每个参数分配了正确的 SqlDbType。
我已经针对 SQL Server 2005 数据库测试了 .NET 2.0 应用程序,因此不是与 SQL Server 相关的问题,因此它必须与 .NET 3.5 相关
有任何想法吗?