Существует ли эквивалент
PRINT 'hello world'
который можно вызвать из кода CLR (С#)?
Я пытаюсь вывести некоторую отладочную информацию в свою функцию. Я не могу запустить отладчик VS, потому что это удаленный сервер.
Спасибо!
Существует ли эквивалент
PRINT 'hello world'
который можно вызвать из кода CLR (С#)?
Я пытаюсь вывести некоторую отладочную информацию в свою функцию. Я не могу запустить отладчик VS, потому что это удаленный сервер.
Спасибо!
Сергей,
Ответ заключается в том, что вы не можете сделать эквивалент
PRINT 'Hello World'
изнутри a [SqlFunction()]
. Вы можете сделать это, однако, из [SqlProcedure()]
, используя
SqlContext.Pipe.Send("hello world")
Это согласуется с T-SQL, где вы получите ошибку "Недопустимое использование оператора" PRINT "в функции", если вы вставляете PRINT внутри функции. Но если вы сделаете это из хранимой процедуры.
Для обходных решений я предлагаю:
List<string> messages
, и напишите другую функцию, которая возвращает содержимое messages
. Конечно, доступ к messages
должен быть синхронизирован, потому что несколько потоков могут пытаться получить к нему доступ в одно и то же время.[SqlProcedure()]
Вы должны просто иметь возможность:
SqlContext.Pipe.Send("hello world");
Если вы используете это в UDF CLR, SqlContext.Pipe
всегда будет null
, как вы обнаружили. Без действительного SqlPipe
я не верю, что вы можете делать то, что хотите.
Если это чисто для целей отладки, вы всегда можете открыть файл в управляемом коде и написать там свой вывод. Это требует, чтобы ваша сборка имела разрешение EXTERNAL_ACCESS
, и это, в свою очередь, требует, чтобы база данных была отмечена как заслуживающая доверия. Не обязательно то, что я сделал бы или рекомендую.
Ahh, я вижу... Jsut, чтобы уточнить: если у вас есть SqlFunction, то SqlContext.Pipe недоступен, однако в SqlProcedure он есть, и вы можете использовать Send() для записи сообщений.
Я все еще не нашел способ вывода информации из SqlFunction, кроме сообщения об исключении.
Вы можете попытаться поместить эту информацию через хранимую процедуру "xp_logevent". Вы можете настроить свою отладочную информацию как "информацию", "предупреждение" или "ошибку" на другом уровне. Я также попытался поместить эту информацию об ошибках/ошибках в журнал событий, но для этого требуется небольшая конфигурация при безопасности, и я сомневаюсь, что не могу ее использовать при производстве.
Функции SQLCLR - Скалярные пользовательские функции (UDF), Табличные функции (TVF), Пользовательские агрегаты (UDA) и методы в пользовательских типах (UDT) - при использовании контекстного соединения ( т.е. ConnectionString = "Context Connection = true;"
), привязаны к большинству тех же ограничений, с которыми связаны функции T-SQL, включая невозможность PRINT
или RAISERROR('message', 10, 1)
. Однако у вас есть несколько вариантов.
Прежде чем перейти к этим опциям, следует сказать, что:
вам не нужно переключаться на использование хранимой процедуры. Если требуется функция, то придерживайтесь функции.
добавление параметра "debug" и изменение вывода для этого кажется немного экстремальным, поскольку функции UDF (T-SQL и SQLCLR) не допускают перегрузки. Следовательно, параметр отладки всегда будет в сигнатуре. Если вы хотите вызвать отладку, просто создайте временную таблицу с именем #debug
(или что-то в этом роде) и протестируйте ее через SELECT OBJECT_ID(N'tempdb..#debug');
с помощью "Context Connection = true;"
для ConnectionString (которая быстро и может быть выполнена в режиме SAFE и является частью той же сессии, чтобы она могла видеть таблицу temp). Получите результат от if (SqlCommand.ExecuteScalar() == DBNull.Value)
.
пожалуйста, не используйте глобальную (то есть статическую) переменную. это намного сложнее, чем необходимо, и требует (как правило), чтобы установка была установлена на UNSAFE
, чего следует избегать, если это вообще возможно.
Итак, если вы можете хотя бы установить сборку на EXTERNAL_ACCESS
, у вас есть несколько вариантов. И для этого не требуется установка базы данных на TRUSTWORTHY ON
. Это очень распространенное (и неудачное) недоразумение. Вам просто нужно подписать сборку (что в любом случае является хорошей практикой), а затем создать асимметричный ключ (в [master]
) из DLL, а затем создать вход на основе этого асимметричного ключа и, наконец, предоставить Login EXTERNAL ACCESS ASSEMBLY
. После этого (один раз) вы можете выполнить любое из следующих действий:
напишите сообщения в файл, используя File.AppendAllText(String path, String contents). Конечно, если у вас нет доступа к файловой системе, это не так полезно. Если в сети есть общий диск, доступ к которому возможен, то до тех пор, пока учетная запись службы для службы 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);
}
Следует отметить, что в каждом из этих трех случаев предполагается, что тестирование выполняется однопоточным способом. Если функция будет выполняться одновременно с нескольких сеансов, вам нужен способ разделить сообщения. В этом случае вы можете получить текущий "transaction_id" (все запросы, даже без 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 для Создать пользовательские функции (механизм базы данных), а затем отредактирован мной, как указано, чтобы отразить различия между функциями 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:
- НАЧАТЬ ДИАЛОГ СООБЩЕНИЯ
- КОНЕЦ КОНВЕРСА
- ПОЛУЧИТЬ ГРУППУ РАЗГОВОРОВ
- ДВИЖЕНИЕ ДВИЖЕНИЯ
- ПОЛУЧИТЬ
- SEND
Следующее относится как к функциям T-SQL, так и к функциям SQLCLR:
- Невозможно использовать PRINT
- Невозможно вызвать NEWID() [Ну, если вы не
SELECT NEWID()
из представления. Но внутри кода .NET вы можете использоватьGuid.NewGuid()
. ]
Ниже перечислены только функции T-SQL:
- Пользовательские функции не могут вызывать хранимую процедуру, но могут вызывать расширенную хранимую процедуру.
- Пользовательские функции не могут использовать динамические таблицы SQL или temp. Разрешены переменные таблицы.
Напротив, функции SQLCLR могут:
- Выполнять хранимые процедуры, если они доступны только для чтения.
- Использовать динамический SQL (все SQL, представленные из SQLCLR, являются ad hoc/dynamic по самой своей природе).
- SELECT из временных таблиц.