Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
A causa dell'integrazione di SQL Server con .NET Framework Common Language Runtime (CLR), è possibile usare qualsiasi linguaggio .NET Framework per creare trigger CLR. Questa sezione illustra le informazioni specifiche dei trigger implementati con l'integrazione con CLR. Per una descrizione completa dei trigger, vedere Trigger DDL.
Che cosa sono i trigger?
Un trigger è un tipo speciale di stored procedure che viene eseguito automaticamente quando viene eseguito un evento di linguaggio. SQL Server include due tipi generali di trigger: DML (Data Manipulation Language) e trigger DDL (Data Definition Language). I trigger DML possono essere usati quando INSERTle istruzioni , UPDATEo DELETE modificano i dati in una tabella o vista specificata. I trigger DDL attivano stored procedure in risposta a un'ampia gamma di istruzioni DDL, che sono principalmente istruzioni che iniziano con CREATE, ALTERe DROP. I trigger DDL possono essere usati per attività amministrative, ad esempio il controllo e la regolazione delle operazioni del database.
Funzionalità univoche dei trigger CLR
I trigger scritti in Transact-SQL hanno la possibilità di determinare quali colonne della vista o della tabella di attivazione sono state aggiornate usando le UPDATE(column) funzioni e COLUMNS_UPDATED() .
I trigger scritti in un linguaggio CLR differiscono da altri oggetti di integrazione CLR in diversi modi significativi. I trigger CLR possono:
Dati di riferimento nelle
INSERTEDtabelle eDELETEDDeterminare quali colonne sono state modificate in seguito a un'operazione
UPDATEAccedere alle informazioni sugli oggetti di database interessati dall'esecuzione di istruzioni DDL.
Queste funzionalità vengono fornite intrinsecamente nel linguaggio di query o dalla SqlTriggerContext classe . Per informazioni sui vantaggi dell'integrazione con CLR e sulla scelta tra codice gestito e Transact-SQL, vedere Panoramica dell'integrazione con CLR.
Uso della classe SqlTriggerContext
La SqlTriggerContext classe non può essere costruita pubblicamente e può essere ottenuta solo accedendo alla SqlContext.TriggerContext proprietà all'interno del corpo di un trigger CLR. La SqlTriggerContext classe può essere ottenuta dall'oggetto attivo SqlContext chiamando la SqlContext.TriggerContext proprietà :
SqlTriggerContext myTriggerContext = SqlContext.TriggerContext;
La SqlTriggerContext classe fornisce informazioni di contesto sul trigger. Queste informazioni contestuali includono il tipo di azione che ha causato l'attivazione del trigger, le colonne modificate in un'operazione UPDATE e, nel caso di un trigger DDL, una struttura XML EventData che descrive l'operazione di attivazione. Per altre informazioni, vedere EVENTDATA (Transact-SQL).
Determinazione dell'azione trigger
Dopo aver ottenuto un SqlTriggerContextoggetto , è possibile usarlo per determinare il tipo di azione che ha causato l'attivazione del trigger. Queste informazioni sono disponibili tramite la TriggerAction proprietà della SqlTriggerContext classe .
Per i trigger DML, la TriggerAction proprietà può essere uno dei valori seguenti:
TriggerAction.Update (0x1)
TriggerAction.Insert (0x2)
TriggerAction.Delete(0x3)
Per i trigger DDL, l'elenco dei possibili valori triggerAction è notevolmente più lungo. Per altre informazioni, vedere "TriggerAction Enumeration" (Enumerazione TriggerAction) in .NET Framework SDK.
Utilizzo delle tabelle inserite ed eliminate
Nelle istruzioni trigger DML vengono usate due tabelle speciali: la tabella inserita e la tabella eliminata . SQL Server crea e gestisce queste tabelle automaticamente. È possibile usare queste tabelle temporanee per testare gli effetti di determinate modifiche ai dati e per impostare le condizioni per le azioni trigger DML; Tuttavia, non è possibile modificare direttamente i dati nelle tabelle.
I trigger CLR possono accedere alle tabelle inserite ed eliminate tramite il provider in-process CLR. Questa operazione viene eseguita ottenendo un SqlCommand oggetto dall'oggetto SqlContext. Per esempio:
C#
SqlConnection connection = new SqlConnection ("context connection = true");
connection.Open();
SqlCommand command = connection.CreateCommand();
command.CommandText = "SELECT * from " + "inserted";
Visual Basic
Dim connection As New SqlConnection("context connection=true")
Dim command As SqlCommand
connection.Open()
command = connection.CreateCommand()
command.CommandText = "SELECT * FROM " + "inserted"
Determinazione delle colonne aggiornate
È possibile determinare il numero di colonne modificate da un'operazione UPDATE utilizzando la ColumnCount proprietà dell'oggetto SqlTriggerContext . È possibile utilizzare il IsUpdatedColumn metodo , che accetta il numero ordinale di colonna come parametro di input, per determinare se la colonna è stata aggiornata. Un True valore indica che la colonna è stata aggiornata.
Ad esempio, questo frammento di codice (dal trigger EmailAudit più avanti in questo argomento) elenca tutte le colonne aggiornate:
C#
reader = command.ExecuteReader();
reader.Read();
for (int columnNumber = 0; columnNumber < triggContext.ColumnCount; columnNumber++)
{
pipe.Send("Updated column "
+ reader.GetName(columnNumber) + "? "
+ triggContext.IsUpdatedColumn(columnNumber).ToString());
}
reader.Close();
Visual Basic
reader = command.ExecuteReader()
reader.Read()
Dim columnNumber As Integer
For columnNumber=0 To triggContext.ColumnCount-1
pipe.Send("Updated column " & reader.GetName(columnNumber) & _
"? " & triggContext.IsUpdatedColumn(columnNumber).ToString() )
Next
reader.Close()
Accesso a EventData per trigger CLR DDL
I trigger DDL, come i trigger regolari, attivano stored procedure in risposta a un evento. A differenza dei trigger DML, tuttavia, non vengono attivati in risposta alle istruzioni UPDATE, INSERT o DELETE in una tabella o vista. Vengono invece attivati in risposta a un'ampia gamma di istruzioni DDL, che sono principalmente istruzioni che iniziano con CREATE, ALTER e DROP. I trigger DDL possono essere usati per attività amministrative, ad esempio il controllo e il monitoraggio delle operazioni del database e delle modifiche dello schema.
Le informazioni su un evento che attiva un trigger DDL sono disponibili nella EventData proprietà della SqlTriggerContext classe . Questa proprietà contiene un xml valore. Lo schema xml include informazioni su:
Ora dell'evento.
ID del processo di sistema (SPID) della connessione durante la quale il trigger è stato eseguito.
Tipo di evento che ha attivato il trigger.
A seconda del tipo di evento, lo schema include informazioni aggiuntive, ad esempio il database in cui si è verificato l'evento, l'oggetto su cui si è verificato l'evento e il comando Transact-SQL dell'evento.
Nell'esempio seguente il trigger DDL seguente restituisce la proprietà raw EventData .
Annotazioni
L'invio di risultati e messaggi tramite l'oggetto SqlPipe viene mostrato qui solo a scopo illustrativo ed è generalmente sconsigliato per il codice di produzione quando si programmano trigger CLR. I dati aggiuntivi restituiti possono essere imprevisti e causare errori dell'applicazione.
C#
using System;
using System.Data;
using System.Data.Sql;
using Microsoft.SqlServer.Server;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Xml;
using System.Text.RegularExpressions;
public class CLRTriggers
{
public static void DropTableTrigger()
{
SqlTriggerContext triggContext = SqlContext.TriggerContext;
switch(triggContext.TriggerAction)
{
case TriggerAction.DropTable:
SqlContext.Pipe.Send("Table dropped! Here's the EventData:");
SqlContext.Pipe.Send(triggContext.EventData.Value);
break;
default:
SqlContext.Pipe.Send("Something happened! Here's the EventData:");
SqlContext.Pipe.Send(triggContext.EventData.Value);
break;
}
}
}
Visual Basic
Imports System
Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
Imports System.Data.SqlClient
'The Partial modifier is only required on one class definition per project.
Partial Public Class CLRTriggers
Public Shared Sub DropTableTrigger()
Dim triggContext As SqlTriggerContext
triggContext = SqlContext.TriggerContext
Select Case triggContext.TriggerAction
Case TriggerAction.DropTable
SqlContext.Pipe.Send("Table dropped! Here's the EventData:")
SqlContext.Pipe.Send(triggContext.EventData.Value)
Case Else
SqlContext.Pipe.Send("Something else happened! Here's the EventData:")
SqlContext.Pipe.Send(triggContext.EventData.Value)
End Select
End Sub
End Class
L'output di esempio seguente è il valore della EventData proprietà dopo un trigger DDL generato da un CREATE TABLE evento:
<EVENT_INSTANCE><PostTime>2004-04-16T21:17:16.160</PostTime><SPID>58</SPID><EventType>CREATE_TABLE</EventType><ServerName>MACHINENAME</ServerName><LoginName>MYDOMAIN\myname</LoginName><UserName>MYDOMAIN\myname</UserName><DatabaseName>AdventureWorks</DatabaseName><SchemaName>dbo</SchemaName><ObjectName>UserName</ObjectName><ObjectType>TABLE</ObjectType><TSQLCommand><SetOptions ANSI_NULLS="ON" ANSI_NULL_DEFAULT="ON" ANSI_PADDING="ON" QUOTED_IDENTIFIER="ON" ENCRYPTED="FALSE" /><CommandText>create table dbo.UserName ( UserName varchar(50), RealName varchar(50) ) </CommandText></TSQLCommand></EVENT_INSTANCE>
Oltre alle informazioni accessibili tramite la SqlTriggerContext classe , le query possono comunque fare riferimento a COLUMNS_UPDATED e inserite/eliminate all'interno del testo di un comando eseguito in-process.
Trigger CLR di esempio
In questo esempio si consideri lo scenario in cui si consente all'utente di scegliere qualsiasi ID desiderato, ma si vuole conoscere gli utenti che hanno immesso in modo specifico un indirizzo di posta elettronica come ID. Il trigger seguente rileva le informazioni e lo registra in una tabella di controllo.
Annotazioni
L'invio di risultati e messaggi tramite l'oggetto SqlPipe è illustrato qui solo a scopo illustrativo ed è generalmente sconsigliato per il codice di produzione. I dati aggiuntivi restituiti potrebbero essere imprevisti e causare errori dell'applicazione
using System;
using System.Data;
using System.Data.Sql;
using Microsoft.SqlServer.Server;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Xml;
using System.Text.RegularExpressions;
public class CLRTriggers
{
[SqlTrigger(Name = @"EmailAudit", Target = "[dbo].[Users]", Event = "FOR INSERT, UPDATE, DELETE")]
public static void EmailAudit()
{
string userName;
string realName;
SqlCommand command;
SqlTriggerContext triggContext = SqlContext.TriggerContext;
SqlPipe pipe = SqlContext.Pipe;
SqlDataReader reader;
switch (triggContext.TriggerAction)
{
case TriggerAction.Insert:
// Retrieve the connection that the trigger is using
using (SqlConnection connection
= new SqlConnection(@"context connection=true"))
{
connection.Open();
command = new SqlCommand(@"SELECT * FROM INSERTED;",
connection);
reader = command.ExecuteReader();
reader.Read();
userName = (string)reader[0];
realName = (string)reader[1];
reader.Close();
if (IsValidEMailAddress(userName))
{
command = new SqlCommand(
@"INSERT [dbo].[UserNameAudit] VALUES ('"
+ userName + @"', '" + realName + @"');",
connection);
pipe.Send(command.CommandText);
command.ExecuteNonQuery();
pipe.Send("You inserted: " + userName);
}
}
break;
case TriggerAction.Update:
// Retrieve the connection that the trigger is using
using (SqlConnection connection
= new SqlConnection(@"context connection=true"))
{
connection.Open();
command = new SqlCommand(@"SELECT * FROM INSERTED;",
connection);
reader = command.ExecuteReader();
reader.Read();
userName = (string)reader[0];
realName = (string)reader[1];
pipe.Send(@"You updated: '" + userName + @"' - '"
+ realName + @"'");
for (int columnNumber = 0; columnNumber < triggContext.ColumnCount; columnNumber++)
{
pipe.Send("Updated column "
+ reader.GetName(columnNumber) + "? "
+ triggContext.IsUpdatedColumn(columnNumber).ToString());
}
reader.Close();
}
break;
case TriggerAction.Delete:
using (SqlConnection connection
= new SqlConnection(@"context connection=true"))
{
connection.Open();
command = new SqlCommand(@"SELECT * FROM DELETED;",
connection);
reader = command.ExecuteReader();
if (reader.HasRows)
{
pipe.Send(@"You deleted the following rows:");
while (reader.Read())
{
pipe.Send(@"'" + reader.GetString(0)
+ @"', '" + reader.GetString(1) + @"'");
}
reader.Close();
//alternately, to just send a tabular resultset back:
//pipe.ExecuteAndSend(command);
}
else
{
pipe.Send("No rows affected.");
}
}
break;
}
}
public static bool IsValidEMailAddress(string email)
{
return Regex.IsMatch(email, @"^([\w-]+\.)*?[\w-]+@[\w-]+\.([\w-]+\.)*?[\w]+$");
}
}
Visual Basic
Imports System
Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
Imports System.Data.SqlClient
Imports System.Text.RegularExpressions
'The Partial modifier is only required on one class definition per project.
Partial Public Class CLRTriggers
<SqlTrigger(Name:="EmailAudit", Target:="[dbo].[Users]", Event:="FOR INSERT, UPDATE, DELETE")> _
Public Shared Sub EmailAudit()
Dim userName As String
Dim realName As String
Dim command As SqlCommand
Dim triggContext As SqlTriggerContext
Dim pipe As SqlPipe
Dim reader As SqlDataReader
triggContext = SqlContext.TriggerContext
pipe = SqlContext.Pipe
Select Case triggContext.TriggerAction
Case TriggerAction.Insert
Using connection As New SqlConnection("context connection=true")
connection.Open()
command = new SqlCommand("SELECT * FROM INSERTED;", connection)
reader = command.ExecuteReader()
reader.Read()
userName = CType(reader(0), String)
realName = CType(reader(1), String)
reader.Close()
If IsValidEmailAddress(userName) Then
command = New SqlCommand("INSERT [dbo].[UserNameAudit] VALUES ('" & _
userName & "', '" & realName & "');", connection)
pipe.Send(command.CommandText)
command.ExecuteNonQuery()
pipe.Send("You inserted: " & userName)
End If
End Using
Case TriggerAction.Update
Using connection As New SqlConnection("context connection=true")
connection.Open()
command = new SqlCommand("SELECT * FROM INSERTED;", connection)
reader = command.ExecuteReader()
reader.Read()
userName = CType(reader(0), String)
realName = CType(reader(1), String)
pipe.Send("You updated: " & userName & " - " & realName)
Dim columnNumber As Integer
For columnNumber=0 To triggContext.ColumnCount-1
pipe.Send("Updated column " & reader.GetName(columnNumber) & _
"? " & triggContext.IsUpdatedColumn(columnNumber).ToString() )
Next
reader.Close()
End Using
Case TriggerAction.Delete
Using connection As New SqlConnection("context connection=true")
connection.Open()
command = new SqlCommand("SELECT * FROM DELETED;", connection)
reader = command.ExecuteReader()
If reader.HasRows Then
pipe.Send("You deleted the following rows:")
While reader.Read()
pipe.Send( reader.GetString(0) & ", " & reader.GetString(1) )
End While
reader.Close()
' Alternately, just send a tabular resultset back:
' pipe.ExecuteAndSend(command)
Else
pipe.Send("No rows affected.")
End If
End Using
End Select
End Sub
Public Shared Function IsValidEMailAddress(emailAddress As String) As Boolean
return Regex.IsMatch(emailAddress, "^([\w-]+\.)*?[\w-]+@[\w-]+\.([\w-]+\.)*?[\w]+$")
End Function
End Class
Supponendo che esistano due tabelle con le definizioni seguenti:
CREATE TABLE Users
(
UserName nvarchar(200) NOT NULL,
RealName nvarchar(200) NOT NULL
);
GO CREATE TABLE UserNameAudit
(
UserName nvarchar(200) NOT NULL,
RealName nvarchar(200) NOT NULL
)
L'istruzione Transact-SQL che crea il trigger in SQL Server è la seguente e presuppone che l'assembly SQLCLRTest sia già registrato nel database DI SQL Server corrente.
CREATE TRIGGER EmailAudit
ON Users
FOR INSERT, UPDATE, DELETE
AS
EXTERNAL NAME SQLCLRTest.CLRTriggers.EmailAudit
Convalida e annullamento di transazioni non valide
L'uso di trigger per convalidare e annullare transazioni INSERT, UPDATE o DELETE non valide o per impedire modifiche allo schema del database è comune. Questa operazione può essere eseguita incorporando la logica di convalida nel trigger e quindi eseguendo il rollback della transazione corrente se l'azione non soddisfa i criteri di convalida.
Quando viene chiamato all'interno di un trigger, il Transaction.Rollback metodo o sqlCommand con il testo del comando "TRANSACTION ROLLBACK" genera un'eccezione con un messaggio di errore ambiguo e deve essere sottoposto a wrapping in un blocco try/catch. Il messaggio di errore visualizzato è simile al seguente:
Msg 6549, Level 16, State 1, Procedure trig_InsertValidator, Line 0
A .NET Framework error occurred during execution of user defined routine or aggregate 'trig_InsertValidator':
System.Data.SqlClient.SqlException: Transaction is not allowed to roll back inside a user defined routine, trigger or aggregate because the transaction is not started in that CLR level. Change application logic to enforce strict transaction nesting... User transaction, if any, will be rolled back.
Questa eccezione è prevista e il blocco try/catch è necessario per continuare a eseguire il codice. Al termine dell'esecuzione del codice del trigger, viene generata un'altra eccezione
Msg 3991, Level 16, State 1, Procedure trig_InsertValidator, Line 1
The context transaction which was active before entering user defined routine, trigger or aggregate "trig_InsertValidator" has been ended inside of it, which is not allowed. Change application logic to enforce strict transaction nesting.
The statement has been terminated.
Questa eccezione è prevista e un blocco try/catch intorno all'istruzione Transact-SQL che esegue l'azione che attiva il trigger è necessario in modo che l'esecuzione possa continuare. Nonostante le due eccezioni generate, viene eseguito il rollback della transazione e le modifiche non vengono sottoposte a commit nella tabella. Una differenza importante tra i trigger CLR e i trigger Transact-SQL è che i trigger Transact-SQL possono continuare a eseguire più lavoro dopo il rollback della transazione.
Esempio
Il trigger seguente esegue una semplice convalida delle istruzioni INSERT in una tabella. Se il valore intero inserito è uguale a uno, viene eseguito il rollback della transazione e il valore non viene inserito nella tabella. Tutti gli altri valori integer vengono inseriti nella tabella. Prendere nota del blocco try/catch intorno al Transaction.Rollback metodo . Lo script Transact-SQL crea una tabella di test, un assembly e una stored procedure gestita. Si noti che le due istruzioni INSERT vengono incluse in un blocco try/catch in modo che l'eccezione generata al termine dell'esecuzione del trigger venga intercettata.
C#
using System;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;
using System.Transactions;
public partial class Triggers
{
// Enter existing table or view for the target and uncomment the attribute line
// [Microsoft.SqlServer.Server.SqlTrigger (Name="trig_InsertValidator", Target="Table1", Event="FOR INSERT")]
public static void trig_InsertValidator()
{
using (SqlConnection connection = new SqlConnection(@"context connection=true"))
{
SqlCommand command;
SqlDataReader reader;
int value;
// Open the connection.
connection.Open();
// Get the inserted value.
command = new SqlCommand(@"SELECT * FROM INSERTED", connection);
reader = command.ExecuteReader();
reader.Read();
value = (int)reader[0];
reader.Close();
// Rollback the transaction if a value of 1 was inserted.
if (1 == value)
{
try
{
// Get the current transaction and roll it back.
Transaction trans = Transaction.Current;
trans.Rollback();
}
catch (SqlException ex)
{
// Catch the expected exception.
}
}
else
{
// Perform other actions here.
}
// Close the connection.
connection.Close();
}
}
}
Visual Basic
Imports System
Imports System.Data.SqlClient
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
Imports System.Transactions
Partial Public Class Triggers
' Enter existing table or view for the target and uncomment the attribute line
' <Microsoft.SqlServer.Server.SqlTrigger(Name:="trig_InsertValidator", Target:="Table1", Event:="FOR INSERT")> _
Public Shared Sub trig_InsertValidator ()
Using connection As New SqlConnection("context connection=true")
Dim command As SqlCommand
Dim reader As SqlDataReader
Dim value As Integer
' Open the connection.
connection.Open()
' Get the inserted value.
command = New SqlCommand("SELECT * FROM INSERTED", connection)
reader = command.ExecuteReader()
reader.Read()
value = CType(reader(0), Integer)
reader.Close()
' Rollback the transaction if a value of 1 was inserted.
If value = 1 Then
Try
' Get the current transaction and roll it back.
Dim trans As Transaction
trans = Transaction.Current
trans.Rollback()
Catch ex As SqlException
' Catch the exception.
End Try
Else
' Perform other actions here.
End If
' Close the connection.
connection.Close()
End Using
End Sub
End Class
Transact-SQL
-- Create the test table, assembly, and trigger.
CREATE TABLE Table1(c1 int);
go
CREATE ASSEMBLY ValidationTriggers from 'E:\programming\ ValidationTriggers.dll';
go
CREATE TRIGGER trig_InsertValidator
ON Table1
FOR INSERT
AS EXTERNAL NAME ValidationTriggers.Triggers.trig_InsertValidator;
go
-- Use a Try/Catch block to catch the expected exception
BEGIN TRY
INSERT INTO Table1 VALUES(42)
INSERT INTO Table1 VALUES(1)
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS ErrorNum, ERROR_MESSAGE() AS ErrorMessage
END CATCH;
-- Clean up.
DROP TRIGGER trig_InsertValidator;
DROP ASSEMBLY ValidationTriggers;
DROP TABLE Table1;
Vedere anche
CREATE TRIGGER (Transact-SQL)
Trigger DML
Trigger DDL
PROVARE... CATCH (Transact-SQL)
Compilazione di oggetti di database con integrazione CLR (Common Language Runtime)
EVENTDATA (Transact-SQL)