web-dev-qa-db-ja.com

SQL CLR関数からメッセージを印刷する方法

同等のものはありますか

PRINT 'hello world'

cLR(C#)コードから呼び出すことができるものはどれですか。

関数にデバッグ情報を出力しようとしています。これはリモートサーバーであるため、VSデバッガーを実行できません。

ありがとう!

35
Serguei

答えはあなたが同等のことを行うことはできないということです

_PRINT 'Hello World'
_

[SqlFunction()]の中から。ただし、[SqlProcedure()]を使用してそれを行うことができます

_SqlContext.Pipe.Send("hello world")
_

これはT-SQLと一貫しており、関数内にPRINTを貼り付けると、「関数内での副作用演算子 'PRINT'の無効な使用」というエラーが発生します。ただし、ストアドプロシージャから行う場合はできません。

回避策については、次のことをお勧めします。

  1. コードからDebug.Printを使用し、SQLサーバーにデバッガーを接続します(説明したとおり、これは機能しません)。
  2. メッセージをグローバル変数、たとえば_List<string> messages_に保存し、messagesの内容を返す別のテーブル値関数を記述します。もちろん、いくつかのスレッドが同時にアクセスしようとする可能性があるため、messagesへのアクセスは同期する必要があります。
  3. コードを[SqlProcedure()]に移動します
  4. = 1の場合、関数が返されたテーブルの一部としてメッセージを返すパラメーター「デバッグ」を追加します(テキストのある列があると想定しています。)
33
Nestor

あなただけができるはずです:

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

これをCLR UDF内で実行している場合は、SqlContext.Pipeは、あなたが発見したとおり、常にnullになります。有効なSqlPipeがないと、あなたが望むことをできるとは思えません。

これが純粋にデバッグ目的である場合は、常にマネージコード内のファイルを開いて、そこに出力を書き込むことができます。これには、アセンブリにEXTERNAL_ACCESS許可。ただし、データベースを信頼できるものとしてマークする必要があります。必ずしも私が行うことや推奨することではありません。

10
Sean Bright

Ahh I see ... Jsut to clarify:you have if a SqlFunction then SqlContext.Pipe is not available、but in a SqlProcedure is it and you can send Send()メッセージを書きます。

例外メッセージを除いて、SqlFunctionから情報を出力する方法がまだ見つかりません。

2
Serguei

SQLCLR関数-スカラーユーザー定義関数(UDF)、テーブル値関数(TVF)、ユーザー定義集計(UDA)、およびユーザー定義型(UDT)内のメソッド-コンテキスト接続を使用する場合(つまり、ConnectionString = _"Context Connection = true;"_)は、PRINTまたはRAISERROR('message', 10, 1)を実行できないことなど、T-SQL関数と同じ制限のほとんどに拘束されます。ただし、いくつかのオプションがあります。

これらのオプションに進む前に、次のことを述べておく必要があります。

  • ストアドプロシージャを使用するように切り替える必要はありません。関数が必要な場合は、関数を使い続けます。

  • uDF(T-SQLおよびSQLCLR)関数ではオーバーロードが許可されていないため、「デバッグ」パラメーターを追加して出力を変更することは少し極端に思えます。したがって、デバッグパラメータは常に署名に含まれます。デバッグをトリガーする場合は、_#debug_(またはそのようなもの)と呼ばれる一時テーブルを作成し、ConnectionStringに対して_"Context Connection = true;"_を使用してSELECT OBJECT_ID(N'tempdb..#debug');を介してテストします(これは高速であり、 SAFEモードで実行およびは同じセッションの一部なので、一時テーブルを表示できます)。その結果をif (SqlCommand.ExecuteScalar() == DBNull.Value)から取得します。

  • グローバル(静的)変数は使用しないでください。これは必要以上に複雑であり、(通常)アセンブリをUNSAFEに設定する必要があります。これは可能な限り回避する必要があります。

したがって、少なくともアセンブリを_EXTERNAL_ACCESS_に設定できる場合、いくつかのオプションがあります。そして、これを行うには、データベースを_TRUSTWORTHY ON_に設定する必要はありません。これは非常に一般的な(そして残念なことに)誤解です。アセンブリに署名するだけです(とにかくこれは良い習慣です)、DLLから非対称キー(_[master]_内)を作成し、その非対称キーに基づいてログインを作成し、最後にログイン_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);
    }
    _

これらの3つのケースのそれぞれで、テストがシングルスレッド方式で行われていると想定されていることに注意してください。関数が複数のセッションから同時に実行される場合は、メッセージを分離する方法が必要です。その場合、現在の "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関数から送信されたものです。 .NETコードでtry/catch/finally/throwを使用できます。]
  • SETステートメントは、ユーザー定義関数では使用できません。
  • FOR XML句は使用できません
  • ユーザー定義関数はネストできます。 ...ネストされたレベルは、呼び出された関数が実行を開始すると増分され、呼び出された関数が実行を終了すると減分されます。ユーザー定義関数は、32レベルまでネストできます。
  • 次のService Brokerステートメントは、Transact-SQLユーザー定義関数の定義に含めることはできません。
    • 会話を始める
    • 会話を終了
    • 会話グループを取得
    • MOVE CONVERSATION
    • 受け取る
    • 送信

以下は、T-SQL関数とSQLCLR関数の両方に関係します。

  • PRINTは使用できません
  • ビュー内からSELECT NEWID()を使用しない限り、NEWID()を呼び出すことはできません。ただし、.NETコード内では、Guid.NewGuid()を使用できます。 ]

以下は、T-SQL関数にのみ関係します。

  • ユーザー定義関数はストアドプロシージャを呼び出すことはできませんが、拡張ストアドプロシージャを呼び出すことはできます。
  • ユーザー定義関数は、動的SQLまたは一時テーブルを使用できません。テーブル変数を使用できます。

対照的に、SQLCLR関数は次のことができます。

  • 読み取り専用である限り、ストアドプロシージャを実行します。
  • 動的SQLを利用します(SQLCLRから送信されるすべてのSQLは、その性質上、アドホック/動的です)。
  • 一時テーブルから選択します。
1
Solomon Rutzky

"xp_logevent"ストアドプロシージャを介してこれらの情報を入力することができます。デバッグ情報は、さまざまなレベルで「情報」、「警告」、または「エラー」として設定できます。また、これらのデバッグ/エラー情報をイベントログに記録しようとしましたが、セキュリティの設定が少し必要です。本番環境ではそれを使用できないと思います。

1
barsteng