Как распечатать сообщение из функции SQL CLR? - PullRequest
31 голосов
/ 29 января 2009

Есть ли эквивалент

PRINT 'hello world'

который можно вызвать из кода CLR (C #)?

Я пытаюсь вывести некоторую отладочную информацию в мою функцию. Я не могу запустить отладчик VS, потому что это удаленный сервер.

Спасибо!

Ответы [ 5 ]

30 голосов
/ 21 июля 2009

Ответ в том, что вы не можете сделать эквивалент

PRINT 'Hello World'

изнутри [SqlFunction()]. Однако вы можете сделать это из [SqlProcedure()], используя

SqlContext.Pipe.Send("hello world")

Это согласуется с T-SQL, где вы получите сообщение об ошибке «Недопустимое использование побочного эффекта оператора« PRINT »внутри функции», если вы вставите PRINT внутри функции. Но нет, если вы делаете это из хранимой процедуры.

Для обходных путей я предлагаю:

  1. Используйте Debug.Print из своего кода и подключите отладчик к SQL Server (я знаю, это не работает для вас, как вы объяснили).
  2. Сохраните сообщения в глобальной переменной, например List<string> messages, и напишите другую табличную функцию, которая возвращает содержимое messages. Конечно, доступ к messages должен быть синхронизирован, поскольку несколько потоков могут пытаться получить к нему доступ одновременно.
  3. Переместите свой код на [SqlProcedure()]
  4. Добавьте параметр 'debug', чтобы при = 1 функция возвращала сообщения как часть возвращенной таблицы (при условии, что есть столбец с текстом ..)
10 голосов
/ 29 января 2009

Вы должны просто быть в состоянии сделать:

SqlContext.Pipe.Send("hello world");

Если вы выполняете это в ULF CLR, SqlContext.Pipe всегда будет null, как вы обнаружили. Без действительного SqlPipe Я не верю, что вы можете делать то, что вы хотите.

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

2 голосов
/ 30 января 2009

Ааа ... Понятно ... Просто уточнить: если у вас есть SqlFunction , тогда SqlContext.Pipe недоступен, однако в SqlProcedure она есть, и вы можете использовать Send ( ) писать сообщения.

Я до сих пор не нашел способ вывода информации из SqlFunction, кроме сообщения об исключении.

1 голос
/ 26 августа 2015

Функции SQLCLR - Скалярные пользовательские функции (UDF), Табличные функции (TVF), Пользовательские агрегаты (UDA) и методы в пользовательских типах (UDT) - при использовании подключения к контексту ( то есть ConnectionString = "Context Connection = true;"), связаны большинством тех же ограничений, что и функции T-SQL, включая невозможность PRINT или RAISERROR('message', 10, 1). Однако у вас есть несколько вариантов.

Прежде чем мы перейдем к этим вариантам, следует указать, что:

  • вам не нужно переключаться на использование хранимой процедуры. Если хотите функцию, то придерживайтесь функции.

  • добавление параметра «отладки» и изменение выходных данных для этого кажется немного экстремальным, поскольку функции UDF (T-SQL и SQLCLR) не допускают перегрузки. Следовательно, параметр отладки всегда будет в сигнатуре. Если вы хотите запустить отладку, просто создайте временную таблицу с именем #debug (или что-то в этом роде) и протестируйте ее через SELECT OBJECT_ID(N'tempdb..#debug');, используя "Context Connection = true;" для ConnectionString (что быстро и может быть выполнено в безопасном режиме является частью одного сеанса, поэтому он может видеть временную таблицу). Получите результат этого от if (SqlCommand.ExecuteScalar() == DBNull.Value).

  • пожалуйста, не используйте глобальную (то есть статическую) переменную. это гораздо сложнее, чем необходимо, и требует (как правило), чтобы сборка была установлена ​​на UNSAFE, чего следует избегать, если это вообще возможно.

Итак, если вы можете хотя бы установить сборку на EXTERNAL_ACCESS, тогда у вас есть несколько вариантов. И для этого не требуется установить для базы данных значение TRUSTWORTHY ON. Это очень распространенное (и прискорбное) недоразумение. Вам просто нужно подписать сборку (что в любом случае является хорошей практикой), затем создать асимметричный ключ (в [master]) из DLL, затем создать логин на основе этого асимметричного ключа и, наконец, предоставить логин EXTERNAL ACCESS ASSEMBLY. После этого (один раз) вы можете выполнить любое из следующих действий:

  • записать сообщения в файл, используя File.AppendAllText (String path, String содержимое) . Конечно, если у вас нет доступа к файловой системе, это не так полезно. Если в сети есть общий диск, к которому можно получить доступ, то, пока учетная запись службы для службы SQL Server имеет разрешение на создание и запись файлов в этот общий ресурс, это будет работать. Если есть общий ресурс, к которому у учетной записи службы нет прав, но у вашей учетной записи домена / Active Directory, вы можете заключить этот File.AppendAllText вызов в:

    using (WindowsImpersonationContext _Impersonate = 
                          SqlContext.WindowsIdentity.Impersonate())
    {
       File.AppendAllText("path.txt", _DebugMessage);
        _Impersonate.Undo();
    }
    
  • подключиться к SQL Server и записать сообщения в таблицу. Это может быть текущий / локальный SQL Server или любой другой SQL Server. Вы можете создать таблицу в [tempdb], чтобы она автоматически очищалась при следующем перезапуске SQL Server, но в противном случае будет действовать до этого времени или до тех пор, пока вы его не удалите. Создание регулярного / внешнего соединения позволяет вам делать DML-операторы. Затем вы можете выбрать из таблицы, как вы запускаете функцию.

  • записать сообщения в переменную окружения. Начиная с Vista / Server 2008 переменные окружения точно не ограничены в размерах, хотя на самом деле они не обрабатывают переводы строк. Но любая переменная, установленная в коде .NET, также будет существовать до перезапуска службы SQL Server. И вы можете добавить сообщение, прочитав текущее значение и конкатенируя новое сообщение до конца. Что-то вроде:

    {
      string _Current = System.Environment.GetEnvironmentVariable(_VariableName,
                                      EnvironmentVariableTarget.Process);
    
      System.Environment.SetEnvironmentVariable(
          _VariableName,
          _Current + _DebugMessage,
          EnvironmentVariableTarget.Process);
    }
    

Следует отметить, что в каждом из этих трех случаев предполагается, что тестирование выполняется однопоточным способом. Если функция будет запущена из нескольких сеансов одновременно, то вам нужен способ разделения сообщений. В этом случае вы можете получить текущий идентификатор транзакции (все запросы, даже если BEGIN TRAN не является транзакцией!), Который должен быть согласованным для любого конкретного выполнения (при многократном использовании в одной и той же функции, а также если функция вызывается для каждой строки через несколько строк). Это значение можно использовать в качестве префикса для сообщений, если используются методы переменных файла или среды, или в качестве отдельного поля при сохранении в таблице. Вы можете получить транзакцию, выполнив следующие действия:

int _TransactionID;

using (SqlConnection _Connection = new SqlConnection("Context Connection = true;"))
{
    using (SqlCommand _Command = _Connection.CreateCommand())
    {
        _Command.CommandText = @"
SELECT transaction_id
FROM sys.dm_exec_requests
WHERE session_id = @@SPID;
";

        _Connection.Open();
        _TransactionID = (int)_Command.ExecuteScalar();
    }
}

Дополнительная информация о функциях T-SQL и SQLCLR

Следующий список первоначально был взят со страницы MSDN для Создание пользовательских функций (компонент Database Engine) , а затем отредактирован мной, как уже отмечалось, чтобы отразить различия между функциями T-SQL и функциями SQLCLR. :

  • Пользовательские функции не могут использоваться для выполнения действий, которые изменяют состояние базы данных.
  • Пользовательские функции не могут содержать предложение OUTPUT INTO, целью которого является таблица.
  • Пользовательские функции не могут возвращать несколько результирующих наборов. Используйте хранимую процедуру, если вам нужно вернуть несколько наборов результатов.
  • Обработка ошибок ограничена в пользовательской функции. UDF не поддерживает TRY… CATCH, @@ ERROR или RAISERROR. [ Примечание: это с точки зрения T-SQL, как собственного, так и переданного из функции SQLCLR. Вы можете использовать try / catch / finally / throw в коде .NET. ]
  • Операторы SET недопустимы в определяемой пользователем функции.
  • Предложение FOR XML не допускается
  • Пользовательские функции могут быть вложенными; ... Уровень вложенности увеличивается, когда вызываемая функция начинает выполнение, и уменьшается, когда вызываемая функция заканчивает выполнение. Пользовательские функции могут быть вложены до 32 уровней.
  • Следующие операторы компонента Service Broker не могут быть включены в определение пользовательской функции Transact-SQL:
    • НАЧАЛО ДИАЛОГОВОГО РАЗГОВОРА
    • КОНЕЦ КОНВЕРСАЦИИ
    • GET CONVERSATION GROUP
    • ПЕРЕМЕЩЕНИЕ ПЕРЕМЕЩЕНИЯ
    • ПРИЕМ
    • Отправить

Следующее относится и к функциям T-SQL, и к функциям SQLCLR:

  • Невозможно использовать PRINT
  • Невозможно вызвать NEWID () [Ну, если вы не SELECT NEWID() из вида. Но внутри кода .NET вы можете использовать Guid.NewGuid(). ]

Следующее относится только к функциям T-SQL:

  • Пользовательские функции не могут вызывать хранимую процедуру, но могут вызывать расширенную хранимую процедуру.
  • Пользовательские функции не могут использовать динамический SQL или временные таблицы. Табличные переменные допускаются.

Напротив, функции SQLCLR могут:

  • Выполнять хранимые процедуры, если они доступны только для чтения.
  • Использовать динамический SQL (все SQL, представленные из SQLCLR, являются специальными / динамическими по самой своей природе).
  • ВЫБРАТЬ из временных таблиц.
1 голос
/ 12 сентября 2013

Вы можете попытаться поместить эту информацию через хранимую процедуру "xp_logevent". Вы можете установить свою отладочную информацию как «информация», «предупреждение» или «ошибка» на другом уровне. Я также пытался поместить эту информацию об отладке / ошибках в журнал событий, но для этого требуется немного настроить безопасность, что, я сомневаюсь, я не могу использовать в производственной среде.

...