Как получить список параметров из хранимой процедуры в SQL Server

Используя С# и System.Data.SqlClient, есть способ получить список параметров, которые принадлежат хранимой процедуре на SQL Server, прежде чем я ее действительно выполнил?

У меня есть сценарий "multi-environment", где есть несколько версий одной и той же схемы базы данных. Примерами сред могут быть "Разработка", "Стадия" и "Производство". "Разработка" будет иметь одну версию хранимой процедуры, а "Staging" будет иметь другую.

Все, что я хочу сделать, это проверить, будет ли параметр присутствовать перед передачей ему значения и вызовом хранимой процедуры. Избегая того, что исключение SqlException вместо того, чтобы его поймать, является плюсом для меня.

Joshua

Ответы

Ответ 2

Вам нужен метод 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";

   // ....
}

Ответ 3

Хотя это не совсем то, что вам нужно, вот пример кода, который использует метод 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();
                }
            }
        }
    }

Ответ 4

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

Ответ 5

Вы можете использовать объект SqlCommandBuilder и вызвать метод DeriveParameters.

В основном вам нужно передать ему команду, которая предназначена для вызова сохраненного proc, и она попадет в БД для обнаружения параметров и создаст соответствующие параметры в свойстве Parameters для SqlCommand

EDIT: вы слишком быстро!

Ответ 6

Mark имеет лучшую реализацию DeriveParameters. По его словам, убедитесь, что вы кешируете, как в этот учебник.

Однако, я думаю, что это опасный способ решения вашей исходной проблемы управления версиями базы данных sproc. Если вы собираетесь изменить подпись процедуры, добавив или удалив параметры, вы должны сделать одно из следующих действий:

  • Код в обратном порядке, используя значения по умолчанию (для новых параметров) или просто игнорируя параметр (для удаленных параметров). Это гарантирует, что ваш клиентский код всегда может вызывать любую версию хранимой процедуры.
  • Явная версия процедуры по имени (так что у вас будут my_proc и my_proc_v2). Это гарантирует, что ваш клиентский код и sprocs останутся в синхронизации.

Опираясь на DeriveParameters, чтобы проверить, какая версия sproc, которую вы используете, кажется неправильным инструментом для работы, IMHO.

Ответ 7

Все эти решения ADO.NET обращаются к библиотеке кода с запросом на метаданные базы данных от вашего имени. Если вы так или иначе столкнетесь с этим успехом, возможно, вам стоит просто написать некоторые вспомогательные функции, которые вызывают

Select count(*) from information_schema.parameters 
where ...(proc name =.. param name=...) (pseudo-code)

Или, возможно, даже сгенерировать ваши параметры на основе списка параметров, который вы вернетесь. Этот метод будет работать с несколькими версиями MS SQL, а иногда и с другими базами данных ANSI SQL.

Ответ 8

Я использую DeriveParameters с .NET 1.1 и 2.0 через пару лет и каждый раз работал как шарм.

Теперь я работаю над своим первым назначением с .NET 3.5 и просто нашел и уродливый сюрприз: DeriveParameters создает все параметры с помощью SqlDbType "Вариант", вместо этого правильные SqlDbTypes. Это создает исключение SqlException при попытке выполнить SP с числовыми параметрами, поскольку SQL Server 2005 говорит, что типы sql-вариантов не могут быть преобразованы в значения int (или smallint или numeric).

Я только что протестировал один и тот же код с .NET CF 2.0 и SQL Server 2000 и работал должным образом, назначив правильный SqlDbType для каждого параметра.

Я тестировал приложения .NET 2.0 с базами данных SQL Server 2005, поэтому это не проблема SQL Server, поэтому она должна быть связана с .NET 3.5

Любые идеи?