Freigeben über


Möglicherweise können Sie keine Remoteverbindung mit SQL Server über einen CLR-Trigger herstellen.

Dieser Artikel hilft Ihnen, das Problem zu beheben, bei dem Sie möglicherweise keine Remoteverbindung mit SQL Server über einen CLR-Trigger (Common Language Runtime) herstellen können.

Ursprüngliche Produktversion: SQL Server
Ursprüngliche KB-Nummer: 2000373

Problembeschreibung

Wenn Sie einen CLR-Trigger bereitstellen, der auf Daten von einem Remote-SQL Server mithilfe von Windows-Authentifizierung nach dem Identitätswechsel des Benutzerkontos WindowsImpersonationContextzugreift, erhalten Sie die folgende Fehlermeldung, wenn der Trigger ausgeführt wird.

Msg 6522, Level 16, State 1, Procedure mytrigger, Line 1
Ein .NET Framework-Fehler ist während der Ausführung einer benutzerdefinierten Routine oder eines Aggregats von "mytrigger" aufgetreten:
System.InvalidOperationException: Der Datenzugriff ist in diesem Kontext nicht zulässig. Der Kontext ist entweder eine Funktion oder Methode, die nicht mit DataAccessKind.Read oder SystemDataAccessKind.Read markiert ist, ist ein Rückruf zum Abrufen von Daten aus der FillRow-Methode einer Table Valued Function oder eine UDT-Validierungsmethode.
System.InvalidOperationException:
at System.Data.SqlServer.Internal.ClrLevelContext.CheckSqlAccessReturnCode(SqlAccessReturnCode eRc)
at System.Data.SqlServer.Internal.ClrLevelContext.GetCurrentContext(SmiEventSink sink, Boolean throwIfNotASqlClrThread, Boolean fAllowImpersonation)
at System.Data.SqlServer.Internal.ClrLevelContext.GetCurrentContext(Boolean throwIfNotASqlClrThread, Boolean fAllowImpersonation)
at System.Data.SqlServer.Internal.ClrLevelContext.SuperiorTransaction.Promote()
at System.Transactions.TransactionStatePSPEOperation.PSPEPromote(InternalTransaction tx)
at System.Transactions.TransactionStateDelegatedBase.EnterState(InternalTransaction tx)
at System.Transactions.EnlistableStates.Promote(InternalTransaction tx)
at System.Transactions.Transaction.Promote()
at System.Transactions.TransactionInterop.ConvertToOletxTransaction(Transaction transaction)
at System.Transactions.TransactionInterop.GetExportCookie(Transaction transaction, Byte[] whereabouts)
at System.Data.SqlClient.SqlInternalConnection.GetTransactionCookie(Transaction transaction, Byte[] whereAbouts)
at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
at System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx)
at System.Data.SqlClient.SqlInternalConnectionTds.Activate(Transaction transaction)
at System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction)
at System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject)
at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConn...
Die Anweisung wurde beendet.

Ursache

Dieses Verhalten ist beabsichtigt. CLR-Code, der innerhalb von SQL Server ausgeführt wird, wird immer im Kontext des Prozesskontos aufgerufen. Wenn ein CLR-Trigger, der Code für den Zugriff auf Daten von einem Remote-SQL-Server enthält, ausgeführt wird, fördert SQL Server automatisch die DML- oder DDL-Transaktion zu einer verteilten Transaktion und stellt mithilfe der SQL Server-Identität eine Verbindung mit dem Remoteserver her. WindowsImpersonationContext Wenn verwendet wird, um die Identität des aufrufenden Benutzers für Verbindungen mit Remote-SQL Server zu imitieren, schlägt die Heraufstellung der Kontexttransaktion zu einer Verteilungstransaktion fehl, was zu dem im Abschnitt "Symptome" erwähnten Fehler führt.

Lösung

Wenn Sie die Funktion zum Identitätswechsel der Identität des Aufrufers innerhalb eines SQL CLR-Triggers benötigen, verwalten Sie die Transaktionen explizit in Ihrem Code. Verwenden Sie die TransactionScopeOption.Supress Methode, um die integrierte SQL-Transaktionsbehandlung zu unterdrücken und die Remotetransaktion gemäß Ihren Anforderungen mit Commit oder Rollback zu verwalten. Im Abschnitt "Schritte zum Reproduzieren" finden Sie ein Beispiel dazu, wie Sie dieses Problem reproduzieren können, und ein Beispiel für die Verwendung der vorherigen Methode zum Beheben des Problems.

Schritte zum Reproduzieren

  1. Öffnen Sie SQL Server Management Studio (SSMS), und stellen Sie dann eine Verbindung mit Ihrer Instanz von SQL Server 2008 her.

  2. Erstellen Sie eine Testdatenbank mit dem folgenden Skript.

     CREATE DATABASE dbTriggerTest 
     GO 
     ALTER DATABASE dbTriggerTest SET TRUSTWORTHY ON 
     GO 
     USE dbTriggertest 
     GO 
     CREATE TABLE t(c1 int) 
     GO  
     sp_configure 'clr enabled', 1 
     GO  
     reconfigure 
     GO  
    
  3. Erstellen Sie in Microsoft Visual Studio 2008 ein Visual C#-Projekt mit der SQL Server-Projektvorlage.

  4. Benennen Sie das Projekt SQLCLRTriggerProject.

  5. Wählen Sie im Menü "Projekt" SQLCLRTriggerProject aus, und konfigurieren Sie den Datenbankabschnitt so, dass er auf die datenbank verweist, die zuvor in der Prozedur (dbTriggerTest) erstellt wurde, und legen Sie die Berechtigungsstufe auf "Extern" fest.

  6. Wählen Sie im Menü Projekt die Option Neues Element hinzufügen aus.

  7. Wählen Sie "Trigger " im Dialogfeld "Neues Element hinzufügen" aus.

  8. Geben Sie einen Namen für den neuen Trigger ein.

  9. Ersetzen Sie den Code des neu erstellten Triggers durch das folgende Codebeispiel.

    Problematische Codeauflistung:

    using System; 
    using System.Data; 
    using System.Data.SqlClient; 
    using Microsoft.SqlServer.Server; 
    using System.Security.Principal;  
    
    public partial class Triggers 
    { 
        [Microsoft.SqlServer.Server.SqlTrigger(Name = "mytrigger", Target = "t", Event = "FOR insert")] 
        public static void mytrigger() 
        { 
            WindowsIdentity clientId = null; 
            WindowsImpersonationContext impersonatedUser = null;  
    
            // Get the client ID. 
            clientId = SqlContext.WindowsIdentity;  
    
            // This outer try block is used to thwart exception filter 
            // attacks which would prevent the inner finally 
            // block from executing and resetting the impersonation  
    
            try 
            { 
                try 
                { 
                    impersonatedUser = clientId.Impersonate(); 
                    if (impersonatedUser != null) 
                    { 
                        SqlConnection conn = new SqlConnection(@"Data Source=<Your server name>;Initial Catalog=master;Integrated Security=SSPI"); 
                        conn.Open(); 
                        SqlCommand cmd = conn.CreateCommand(); 
                        cmd.CommandText = "select * from sys.sysobjects"; 
                        cmd.CommandType = CommandType.Text; 
                        cmd.ExecuteNonQuery(); 
                    } 
                } 
                finally 
                { 
                    // Undo impersonation. 
                    if (impersonatedUser != null) 
                        impersonatedUser.Undo(); 
                } 
            } 
            catch 
            { 
                throw; 
            }  
        }  
    }  
    
  10. Stellen Sie das Projekt in Schritt 2 mithilfe der Option "SQLCLR Trigger Project bereitstellen" im Menü "Erstellen " in der Datenbank bereit.

  11. Öffnen Sie SSMS, und stellen Sie dann eine Verbindung mit der Instanz von SQL Server 2008 her, in der der Trigger bereitgestellt wird.

  12. Die folgenden beiden Elemente sollten unter der Testdatenbank dbTriggerTesterstellt werden:

    • Trigger - mytrigger
    • Assemblys – SQLCLRTriggerProject
  13. Überprüfen Sie mithilfe des Eigenschaftenbereichs der Assembly in SSMS, ob die für die SQLCLRTriggerProject Assembly festgelegte Berechtigung den externen Zugriff anzeigt.

  14. Führen Sie die folgende Anweisung aus, um das Problem zu reproduzieren. insert into t values (1)

  15. Ersetzen Sie die problematische Codeauflistung durch das folgende Codebeispiel, um das Problem zu beheben.

    Feste Codeauflistung:

    using System; 
    using System.Data; 
    using System.Data.SqlClient; 
    using Microsoft.SqlServer.Server; 
    using System.Security.Principal; 
    using System.Transactions;  
    
    public partial class Triggers 
    {  
        [Microsoft.SqlServer.Server.SqlTrigger(Name = "mytrigger", Target = "t", Event = "FOR insert")] 
        public static void mytrigger() 
        {  
            using (new TransactionScope(TransactionScopeOption.Suppress)) 
            { 
                WindowsIdentity clientId = null; 
                WindowsImpersonationContext impersonatedUser = null; 
                // Get the client ID. 
                clientId = SqlContext.WindowsIdentity; 
                // This outer try block is used to thwart exception filter 
                // attacks which would prevent the inner finally 
                // block from executing and resetting the impersonation 
                try 
                { 
                    SqlTransaction tran = null;  
                    try 
                    { 
                        impersonatedUser = clientId.Impersonate(); 
                        if (impersonatedUser != null) 
                        {  
                            SqlConnection conn = new SqlConnection(@"Data Source=<Your server name>;Initial Catalog=master;Integrated Security=SSPI");  
                            conn.Open(); 
                            tran = conn.BeginTransaction(); 
                            SqlCommand cmd = conn.CreateCommand(); 
                            cmd.Transaction = tran; 
                            cmd.CommandText = "select * from sys.sysobjects"; 
                            cmd.CommandType = CommandType.Text; 
                            cmd.ExecuteNonQuery(); 
                            tran.Commit(); 
                        } 
                    } 
                    catch (Exception ex) 
                    { 
                        if (null != tran) 
                        tran.Rollback(); 
                        throw ex; 
                    }  
                    finally 
                    { 
                    // Undo impersonation. 
                    if (impersonatedUser != null) 
                    impersonatedUser.Undo(); 
                    } 
                } 
                catch 
                { 
                    throw; 
                }  
            }  
        }
    }