Это допустимый вариант использования SqlCLR?Адаптация результатов хранимых процедур - PullRequest
0 голосов
/ 07 октября 2018

Я уже реализовал хранимую процедуру CLR, которую я опишу ниже, и она работает достаточно хорошо.Но я не уверен, что мне действительно нужен CLR для этого, или решение среднего уровня было бы столь же производительным и обслуживаемым.

В существующей кодовой базе эта компания накопила 500+ поисковых запросовпроцедуры на протяжении многих лет.Теперь они хотят, чтобы я написал механизм агрегации, который работает для всех этих хранимых процедур.Каждая хранимая процедура поиска в их системе имеет сходный формат, поэтому я знаю, как программно вызывать их с правильными параметрами и т. Д.

Я не хочу каким-либо образом изменять каждую хранимую процедуру поиска.Я предпочел бы сначала вставить результаты хранимой процедуры во временную таблицу.Затем я могу запустить механизм агрегирования, запросив временную таблицу.

Проблема в том, что в SQL Server нельзя вставить результаты хранимой процедуры, если вы не знаете схему EXACT результатов хранимой процедуры.Но это на самом деле невозможно, потому что хранимые процедуры могут возвращать разные схемы результатов в зависимости от параметров.

Поэтому, чтобы гарантировать, что хранимая процедура вернет ожидаемую схему EXACT, я создал «SP_Wrapper»"Хранимая процедура CLR.В этой оболочке я вызываю хранимую процедуру и «адаптирую» каждую запись к моей ожидаемой схеме.Затем я возвращаю адаптированный набор результатов.

Затем я могу вставить в свою временную таблицу, зная, что схема была правильной.

Теперь, допустим, я адаптировал результаты на среднем уровне.Я должен был бы сначала вернуть результат на средний уровень.Итерируйте их, адаптируйте каждую запись, затем вставляйте отдельно или массовую копию.

Это казалось правильным выбором, но теперь мне нужно развернуть эту хранимую процедуру CLR.Я действительно много здесь получаю?

using (var conn = new SqlConnection("context connection=true"))
{
        conn.Open();

        //load result table schema
        resultColumns = SqlSchema.getTempTableMeta(conn, resultTableName);

        //load parameter table schema - may not exist
        var hasParams = !String.IsNullOrEmpty(paramTableName);
        parameters = SqlSchema.getTempTableMeta(conn, paramTableName);

        SqlCommand command;
        SqlDataReader reader = null;

        ///Load Parameter Values
        if (hasParams)
        {
            command = conn.CreateCommand();
            command.CommandText = $@"if( object_id('tempdb..{paramTableName}') is not null) select top 1 * from {paramTableName};";
            command.CommandType = CommandType.Text;
            reader = command.ExecuteReader();

            using (reader)
            {
                while (reader.Read())
                {
                    foreach (var p in parameters)
                    {
                        var val = reader[p.Name];

                        if (!String.IsNullOrWhiteSpace(val?.ToString()))
                            parameter_values[p.Name] = val;
                    }
                }
            }
        }

        SqlDataRecord record = new SqlDataRecord(resultColumns.ToArray());

        //////mark the beginning of the result set
        SqlContext.Pipe.SendResultsStart(record);

        command = conn.CreateCommand();
        command.CommandType = CommandType.StoredProcedure;
        command.CommandText = spName;

        foreach (var p in parameters)
        {
            if (parameter_values.ContainsKey(p.Name))
                command.Parameters.Add(
                    new SqlParameter
                    {
                        ParameterName = p.Name,
                        SqlDbType = p.SqlDbType,
                        Value = parameter_values[p.Name]
                    }
                );
        }

        var cmdReader = command.ExecuteReader();

        using (cmdReader)
        {
            while (cmdReader.Read())
            {
                int sequence = 0;
                foreach (var resultColumn in resultColumns)
                {
                    var resultColumnValue = cmdReader[resultColumn.Name];

                    var t = resultColumn.SqlDbType;

                    resultColumnValue = SqlSchema.Convert(resultColumnValue, SqlSchema.sqlTypeMap[t]);

                    record.SetValue(sequence, resultColumnValue);
                    sequence++;
                }
                SqlContext.Pipe.SendResultsRow(record);
            }
        }

        // Mark the end of the result-set.
        SqlContext.Pipe.SendResultsEnd();

        conn.Close();
    }

Ответы [ 2 ]

0 голосов
/ 08 октября 2018

Я бы сказал, что это зависит от:

  1. Что делать с данными, возвращаемыми из хранимых процедур?

    В коде, опубликованном в вопросе, не хватает некоторыхкусочками, так что не совсем понятно, захватываете ли вы все или только некоторые из возвращаемых столбцов.Одно из преимуществ вашего текущего подхода заключается в том, что вы можете игнорировать столбцы, которые вам не интересны, при выгрузке результатов во временную таблицу (таблицы).Генерация кода T-SQL для выполнения INSERT...EXEC в чистом T-SQL не позволит вам отфильтровать целые столбцы;Вы должны вставить все столбцы в таблицу назначения, хотите вы этого или нет.

  2. Существуют ли другие потенциальные возможности использования этого интерфейса с этими поисковыми процессами?

    OneПреимущество подхода SQLCLR заключается в том, что он более доступен для использования.Если эта функциональность находится в коде приложения, то только код приложения может его использовать.Вы не сможете использовать его в задании агента SQL (без вызова кода приложения, для чего может потребоваться написать консольное приложение, указывающее на ту же библиотеку, или сделать библиотеку модулем PowerShell).Вы не сможете использовать его в процедуре, которая является источником для автоматического отчета, отправляемого через Database Mail.Вы не сможете легко расширить его использование в других областях, которые еще не были запрошены.Просто кое-что, чтобы рассмотреть, если любой из этих вариантов использования кажется возможным.

  3. Я не уверен на 100% в этом, но ваш текущий подход может позволить вамустановите ограничение на выполнение INSERT...EXEC, если процесс, который вы выполняете (или один из его подпроцессов, если он есть), имеет INSERT...EXEC.

    (у меня нет времени для тестированияэто прямо сейчас, но когда я это сделаю, если я обнаружу, что это не обходит ограничение, я уберу эту точку.)

Хотя SQLCLR не так простВперед, как T-SQL с точки зрения развертывания / CI, это также не невозможно.Конечно, Visual Studio / SSDT действительно не облегчает автоматизацию развертываний, когда необходимо правильно управлять безопасностью (т. Е. Использовать вход в систему на основе сигнатур вместо включения TRUSTWORTHY), и это необходимо при использовании SQL Server 2017 или новее.Чтобы помочь с этим, я продемонстрирую два аналогичных подхода, которые работают с Visual Studio / SSDT или независимо друг от друга, которые обсуждались в следующих двух моих сообщениях в блоге:

  1. SQLCLR против SQL Server 2017, часть2: «Строгая безопасность CLR» - Решение 1 - больше шагов, чем Часть 3, Решение 2 (ниже), но хорошо подходит для существующих проектов, поскольку практически не требует изменений в существующем решении или даже в процессе развертывания (и вФактически, это фактически тот путь, по которому я пошел для своего SQL # проекта, поскольку все, что он делал, это добавлял 3 простых шага в начало сценария установки).В этом решении для подписи используется асимметричный ключ.
  2. SQLCLR против SQL Server 2017, часть 3: «Строгая безопасность CLR» - решение 2 .Это решение использует сертификат для подписи.

Цель обоих этих решений - не только работать с Visual Studio / SSDT, но и создавать автономный сценарий T-SQL.Скрипт T-SQL не имеет внешних ссылок: ни на файлы DLL, ни на файлы .snk / .cer / .pfx.Это делает сценарий полностью переносимым, и, следовательно, намного проще работать с любой установкой непрерывной интеграции: -).

Для получения дополнительной информации о работе с SQLCLR в целом, пожалуйста, посетите: SQLCLR Info

0 голосов
/ 07 октября 2018

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

Недостатком является то, что код SQL CLR труднее писать, труднее тестировать и сложнее развертывать, чем обычный код.

Прав ли этот компромиссдля вас или нет, зависит от ваших потребностей в производительности и потребностей в производительности разработчиков.Действительно ли это копирование данных занимает столько времени, что стоит потрогать SQL CLR ?!Это может или не может быть.

Другое и более быстрое решение будет генерировать код SQL для каждой процедуры, которую вы должны вызвать.Не пишите это от руки.Вместо этого заставьте инструмент определить точную схему этой процедуры и выведите идеальный T-SQL, который напрямую передает данные в нужном формате в нужное место назначения.

Этот инструмент действительно может быть процедурой SQL CLR, которая генерирует коди затем выполняет это.Или это может быть генератор кода на основе C #.

...