Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
En raison de l’intégration de SQL Server à .NET Framework Common Language Runtime (CLR), vous pouvez utiliser n’importe quel langage .NET Framework pour créer des déclencheurs CLR. Cette section traite des informations spécifiques aux déclencheurs implémentés avec l’intégration clR. Pour une présentation complète des déclencheurs, consultez déclencheurs DDL.
Qu’est-ce que les déclencheurs ?
Un déclencheur est un type spécial de procédure stockée qui s’exécute automatiquement lorsqu’un événement de langage s’exécute. SQL Server inclut deux types généraux de déclencheurs : les déclencheurs DML (Data Manipulation Language) et DDL (Data Definition Language). Les déclencheurs DML peuvent être utilisés lorsque INSERTdes instructions ou DELETE des UPDATEinstructions modifient des données dans une table ou une vue spécifiée. DDL déclenche des procédures stockées en réponse à une variété d’instructions DDL, qui sont principalement des instructions qui commencent par CREATE, ALTERet DROP. Les déclencheurs DDL peuvent être utilisés pour les tâches administratives, telles que l’audit et la régulation des opérations de base de données.
Fonctionnalités uniques des déclencheurs CLR
Les déclencheurs écrits dans Transact-SQL ont la possibilité de déterminer quelles colonnes de la vue de déclenchement ou de la table ont été mises à jour à l’aide des fonctions et COLUMNS_UPDATED() des UPDATE(column) fonctions.
Les déclencheurs écrits dans un langage CLR diffèrent d’autres objets d’intégration CLR de plusieurs façons significatives. Les déclencheurs CLR peuvent :
Données de référence dans les tables et
DELETEDlesINSERTEDtablesDéterminer les colonnes qui ont été modifiées à la suite d’une
UPDATEopérationAccéder aux informations sur les objets de base de données affectés par l’exécution d’instructions DDL.
Ces fonctionnalités sont fournies intrinsèquement dans le langage de requête ou par la SqlTriggerContext classe. Pour plus d’informations sur les avantages de l’intégration clR et le choix entre le code managé et Transact-SQL, consultez Vue d’ensemble de l’intégration clR.
Utilisation de la classe SqlTriggerContext
La SqlTriggerContext classe ne peut pas être construite publiquement et ne peut être obtenue qu’en accédant à la SqlContext.TriggerContext propriété dans le corps d’un déclencheur CLR. La SqlTriggerContext classe peut être obtenue à partir de l’actif SqlContext en appelant la SqlContext.TriggerContext propriété :
SqlTriggerContext myTriggerContext = SqlContext.TriggerContext;
La SqlTriggerContext classe fournit des informations contextuelles sur le déclencheur. Ces informations contextuelles incluent le type d’action qui a provoqué le déclenchement du déclencheur, les colonnes qui ont été modifiées dans une opération UPDATE et, dans le cas d’un déclencheur DDL, une structure XML EventData qui décrit l’opération de déclenchement. Pour plus d’informations, consultez EVENTDATA (Transact-SQL).
Détermination de l’action de déclencheur
Une fois que vous avez obtenu un SqlTriggerContext, vous pouvez l’utiliser pour déterminer le type d’action qui a provoqué le déclenchement du déclencheur. Ces informations sont disponibles via la TriggerAction propriété de la SqlTriggerContext classe.
Pour les déclencheurs DML, la TriggerAction propriété peut être l’une des valeurs suivantes :
TriggerAction.Update (0x1)
TriggerAction.Insert (0x2)
TriggerAction.Delete(0x3)
Pour les déclencheurs DDL, la liste des valeurs TriggerAction possibles est considérablement plus longue. Pour plus d’informations, consultez « TriggerAction Enumeration » (Énumération TriggerAction) dans le Kit de développement logiciel (SDK) .NET Framework.
Utilisation des tables insérées et supprimées
Deux tables spéciales sont utilisées dans les instructions de déclencheur DML : la table insérée et la table supprimée . SQL Server crée et gère automatiquement ces tables. Vous pouvez utiliser ces tables temporaires pour tester les effets de certaines modifications de données et définir des conditions pour les actions de déclencheur DML ; Toutefois, vous ne pouvez pas modifier les données dans les tables directement.
Les déclencheurs CLR peuvent accéder aux tables insérées et supprimées via le fournisseur clR in-process. Pour ce faire, obtenez un SqlCommand objet à partir de l’objet SqlContext. Par exemple:
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"
Détermination des colonnes mises à jour
Vous pouvez déterminer le nombre de colonnes qui ont été modifiées par une opération UPDATE à l’aide de la ColumnCount propriété de l’objet SqlTriggerContext . Vous pouvez utiliser la IsUpdatedColumn méthode, qui prend l’ordinal de colonne comme paramètre d’entrée, pour déterminer si la colonne a été mise à jour. Une True valeur indique que la colonne a été mise à jour.
Par exemple, cet extrait de code (à partir du déclencheur EmailAudit plus loin dans cette rubrique) répertorie toutes les colonnes mises à jour :
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()
Accès à EventData pour les déclencheurs DDL CLR
Les déclencheurs DDL, comme les déclencheurs réguliers, déclenchent des procédures stockées en réponse à un événement. Mais contrairement aux déclencheurs DML, ils ne se déclenchent pas en réponse aux instructions UPDATE, INSERT ou DELETE sur une table ou une vue. Au lieu de cela, ils se déclenchent en réponse à une variété d’instructions DDL, qui sont principalement des instructions qui commencent par CREATE, ALTER et DROP. Les déclencheurs DDL peuvent être utilisés pour les tâches administratives, telles que l’audit et la surveillance des opérations de base de données et des modifications de schéma.
Des informations sur un événement qui déclenche un déclencheur DDL sont disponibles dans la EventData propriété de la SqlTriggerContext classe. Cette propriété contient une xml valeur. Le schéma xml inclut des informations sur les points suivants :
l'heure de l'événement ;
ID de processus système (SPID) de la connexion pendant laquelle le déclencheur a été exécuté.
Type d’événement qui a déclenché le déclencheur.
Ensuite, selon le type d’événement, le schéma inclut des informations supplémentaires, telles que la base de données dans laquelle l’événement s’est produit, l’objet sur lequel l’événement s’est produit et la commande Transact-SQL de l’événement.
Dans l’exemple suivant, le déclencheur DDL suivant retourne la propriété brute EventData .
Remarque
L’envoi de résultats et de messages via l’objet est affiché ici uniquement à des fins d’illustration et est généralement déconseillé pour le code de production lors de la SqlPipe programmation des déclencheurs CLR. Des données supplémentaires retournées peuvent être inattendues et entraîner des erreurs d’application.
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’exemple de sortie suivant est la valeur de propriété EventData après un déclencheur DDL déclenché par un CREATE TABLE événement :
<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>
Outre les informations accessibles par le biais de la SqlTriggerContext classe, les requêtes peuvent toujours faire référence au COLUMNS_UPDATED texte d’une commande exécutée dans le processus et les insérer/les supprimer.
Exemple de déclencheur CLR
Dans cet exemple, considérez le scénario dans lequel vous laissez l’utilisateur choisir l’ID souhaité, mais vous souhaitez connaître les utilisateurs qui ont entré spécifiquement une adresse de messagerie en tant qu’ID. Le déclencheur suivant détecte ces informations et les consigne dans une table d’audit.
Remarque
L’envoi de résultats et de messages via l’objet est affiché ici uniquement à des fins d’illustration et est généralement déconseillé pour le SqlPipe code de production. Des données supplémentaires retournées peuvent être inattendues et entraîner des erreurs d’application
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
En supposant que deux tables existent avec les définitions suivantes :
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’instruction Transact-SQL qui crée le déclencheur dans SQL Server est la suivante et suppose que l’assembly SQLCLRTest est déjà inscrit dans la base de données SQL Server actuelle.
CREATE TRIGGER EmailAudit
ON Users
FOR INSERT, UPDATE, DELETE
AS
EXTERNAL NAME SQLCLRTest.CLRTriggers.EmailAudit
Validation et annulation de transactions non valides
L’utilisation de déclencheurs pour valider et annuler des transactions INSERT, UPDATE ou DELETE non valides ou pour empêcher les modifications apportées à votre schéma de base de données est courante. Pour ce faire, incorporez la logique de validation dans votre déclencheur, puis rétablissez la transaction actuelle si l’action ne répond pas aux critères de validation.
Lorsqu’elle est appelée dans un déclencheur, la Transaction.Rollback méthode ou sqlCommand avec le texte de commande « TRANSACTION ROLLBACK » lève une exception avec un message d’erreur ambigu et doit être encapsulée dans un bloc try/catch. Le message d’erreur que vous voyez est similaire à ce qui suit :
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.
Cette exception est attendue et le bloc try/catch est nécessaire pour que l'exécution du code continue. Lorsque le code de déclencheur termine l’exécution, une autre exception est levée
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.
Cette exception est également attendue et un bloc try/catch autour de l’instruction Transact-SQL qui exécute l’action qui déclenche le déclencheur est nécessaire pour que l’exécution puisse continuer. Malgré les deux exceptions levées, la transaction est restaurée et les modifications ne sont pas validées dans la table. Une différence majeure entre les déclencheurs CLR et les déclencheurs Transact-SQL est que Transact-SQL déclencheurs peuvent continuer à effectuer plus de travail une fois la transaction restaurée.
Exemple :
Le déclencheur suivant effectue une validation simple des instructions INSERT sur une table. Si la valeur entière insérée est égale à une valeur, la transaction est restaurée et la valeur n’est pas insérée dans la table. Toutes les autres valeurs entières sont insérées dans la table. Notez le bloc try/catch autour de la Transaction.Rollback méthode. Le script Transact-SQL crée une table de test, un assembly et une procédure stockée managée. Notez que les deux instructions INSERT sont encapsulées dans un bloc try/catch afin que l’exception levée lorsque le déclencheur termine l’exécution est interceptée.
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;
Voir aussi
CREATE TRIGGER (Transact-SQL)
Déclencheurs DML
Déclencheurs DDL
ESSAYER... CATCH (Transact-SQL)
Génération d’objets de base de données avec intégration clR (Common Language Runtime)
EVENTDATA (Transact-SQL)