Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Debido a la integración de SQL Server con Common Language Runtime (CLR) de .NET Framework, puede usar cualquier lenguaje de .NET Framework para crear desencadenadores CLR. En esta sección se trata información específica de los desencadenadores implementados con la integración clR. Para obtener una explicación completa de los desencadenadores, consulte Desencadenadores DDL.
¿Qué son los desencadenadores?
Un desencadenador es un tipo especial de procedimiento almacenado que se ejecuta automáticamente cuando se ejecuta un evento de lenguaje. SQL Server incluye dos tipos generales de desencadenadores: desencadenadores de lenguaje de manipulación de datos (DML) y lenguaje de definición de datos (DDL). Los desencadenadores DML se pueden usar cuando INSERT
las instrucciones , UPDATE
o DELETE
modifican datos en una tabla o vista especificadas. Los desencadenadores DDL activan procedimientos almacenados en respuesta a una variedad de instrucciones DDL, que son principalmente instrucciones que comienzan por CREATE
, ALTER
y DROP
. Los desencadenadores DDL se pueden usar para tareas administrativas, como la auditoría y la regulación de las operaciones de base de datos.
Funcionalidades únicas de desencadenadores CLR
Los desencadenadores escritos en Transact-SQL tienen la capacidad de determinar qué columnas de la vista de activación o tabla se han actualizado mediante las UPDATE(column)
funciones y COLUMNS_UPDATED()
.
Los desencadenadores escritos en un lenguaje CLR difieren de otros objetos de integración CLR de varias maneras significativas. Los desencadenadores CLR pueden:
Datos de referencia en las
INSERTED
tablas yDELETED
Determinar qué columnas se han modificado como resultado de una
UPDATE
operaciónObtenga acceso a información sobre los objetos de base de datos afectados por la ejecución de instrucciones DDL.
Estas funcionalidades se proporcionan inherentemente en el lenguaje de consulta o en la SqlTriggerContext
clase . Para obtener información sobre las ventajas de la integración clR y elegir entre código administrado y Transact-SQL, consulte Introducción a la integración clR.
Uso de la clase SqlTriggerContext
La SqlTriggerContext
clase no se puede construir públicamente y solo se puede obtener accediendo a la SqlContext.TriggerContext
propiedad dentro del cuerpo de un desencadenador CLR. La SqlTriggerContext
clase se puede obtener del activo SqlContext
llamando a la SqlContext.TriggerContext
propiedad :
SqlTriggerContext myTriggerContext = SqlContext.TriggerContext;
La SqlTriggerContext
clase proporciona información de contexto sobre el desencadenador. Esta información contextual incluye el tipo de acción que provocó que el desencadenador se desencadenara, qué columnas se modificaron en una operación UPDATE y, en el caso de un desencadenador DDL, una estructura XML EventData
que describe la operación de desencadenamiento. Para obtener más información, vea EVENTDATA (Transact-SQL).
Determinar la acción del desencadenador
Una vez que haya obtenido un SqlTriggerContext
, puede usarlo para determinar el tipo de acción que provocó que se desencadenara el desencadenador. Esta información está disponible a través de la TriggerAction
propiedad de la SqlTriggerContext
clase .
Para los desencadenadores DML, la TriggerAction
propiedad puede ser uno de los siguientes valores:
TriggerAction.Update (0x1)
TriggerAction.Insert (0x2)
TriggerAction.Delete(0x3)
En el caso de los desencadenadores DDL, la lista de posibles valores TriggerAction es considerablemente más larga. Consulte "TriggerAction Enumeration" en el SDK de .NET Framework para obtener más información.
Uso de las tablas insertadas y eliminadas
Se usan dos tablas especiales en instrucciones de desencadenador DML: la tabla insertada y la tabla eliminada . SQL Server crea y administra automáticamente estas tablas. Puede usar estas tablas temporales para probar los efectos de determinadas modificaciones de datos y establecer condiciones para acciones de desencadenador DML; sin embargo, no se pueden modificar los datos de las tablas directamente.
Los desencadenadores CLR pueden acceder a las tablas insertadas y eliminadas a través del proveedor de CLR en proceso. Para ello, se obtiene un SqlCommand
objeto del objeto SqlContext. Por ejemplo:
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"
Determinar las columnas actualizadas
Puede determinar el número de columnas modificadas por una operación UPDATE mediante la ColumnCount
propiedad del SqlTriggerContext
objeto . Puede usar el IsUpdatedColumn
método , que toma el ordinal de columna como parámetro de entrada, para determinar si la columna se actualizó. Un True
valor indica que la columna se ha actualizado.
Por ejemplo, este fragmento de código (del desencadenador EmailAudit más adelante en este tema) enumera todas las columnas actualizadas:
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()
Acceso a EventData para desencadenadores DDL de CLR
Los desencadenadores DDL, como los desencadenadores normales, activan procedimientos almacenados en respuesta a un evento. Pero a diferencia de los desencadenadores DML, no se activan en respuesta a instrucciones UPDATE, INSERT o DELETE en una tabla o vista. En su lugar, se activan en respuesta a una variedad de instrucciones DDL, que son principalmente instrucciones que comienzan por CREATE, ALTER y DROP. Los desencadenadores DDL se pueden usar para tareas administrativas, como auditoría y supervisión de operaciones de base de datos y cambios de esquema.
La información sobre un evento que desencadena un desencadenador DDL está disponible en la EventData
propiedad de la SqlTriggerContext
clase . Esta propiedad contiene un xml
valor. El esquema xml incluye información sobre:
La hora del evento.
Identificador de proceso del sistema (SPID) de la conexión durante la cual se ejecutó el desencadenador.
Tipo de evento que desencadenó el desencadenador.
A continuación, dependiendo del tipo de evento, el esquema incluye información adicional, como la base de datos en la que se produjo el evento, el objeto en el que se produjo el evento y el comando Transact-SQL del evento.
En el ejemplo siguiente, el siguiente desencadenador DDL devuelve la propiedad raw EventData
.
Nota:
El envío de resultados y mensajes a través del SqlPipe
objeto se muestra aquí solo con fines ilustrativos y, por lo general, se desaconseja para el código de producción al programar desencadenadores CLR. Los datos adicionales devueltos pueden ser inesperados y provocar errores de aplicación.
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
La siguiente salida de ejemplo es el EventData
valor de propiedad después de que un evento desencadena CREATE TABLE
un desencadenador DDL:
<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>
Además de la información accesible a través de la SqlTriggerContext
clase , las consultas todavía pueden hacer referencia a COLUMNS_UPDATED
y insertarse o eliminar dentro del texto de un comando ejecutado en proceso.
Desencadenador CLR de ejemplo
En este ejemplo, considere el escenario en el que se permite al usuario elegir cualquier identificador que desee, pero desea conocer los usuarios que especificaron específicamente una dirección de correo electrónico como identificador. El siguiente desencadenador detectaría esa información y la registraría en una tabla de auditoría.
Nota:
El envío de resultados y mensajes a través del SqlPipe
objeto se muestra aquí solo con fines ilustrativos y, por lo general, se desaconseja para el código de producción. Los datos adicionales devueltos pueden ser inesperados y provocar errores de aplicación
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
Suponiendo que existen dos tablas con las siguientes definiciones:
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
)
La instrucción Transact-SQL que crea el desencadenador en SQL Server es la siguiente y supone que el ensamblado SQLCLRTest ya está registrado en la base de datos de SQL Server actual.
CREATE TRIGGER EmailAudit
ON Users
FOR INSERT, UPDATE, DELETE
AS
EXTERNAL NAME SQLCLRTest.CLRTriggers.EmailAudit
Validación y cancelación de transacciones no válidas
El uso de desencadenadores para validar y cancelar transacciones INSERT, UPDATE o DELETE no válidas o para evitar cambios en el esquema de la base de datos es común. Esto se puede lograr mediante la incorporación de lógica de validación en el desencadenador y, a continuación, revertir la transacción actual si la acción no cumple los criterios de validación.
Cuando se llama dentro de un desencadenador, el Transaction.Rollback
método o sqlCommand con el texto de comando "TRANSACTION ROLLBACK" produce una excepción con un mensaje de error ambiguo y se debe encapsular en un bloque try/catch. El mensaje de error que ve es similar al siguiente:
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.
Se espera esta excepción y el bloque try/catch resulta necesario para que continúe la ejecución del código. Cuando el código del desencadenador finaliza la ejecución, se genera otra excepción.
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.
Esta excepción también se espera y es necesario un bloque try/catch alrededor de la instrucción Transact-SQL que realiza la acción que desencadena el desencadenador para que la ejecución pueda continuar. A pesar de las dos excepciones iniciadas, la transacción se revierte y los cambios no se confirman en la tabla. Una diferencia importante entre los desencadenadores CLR y los desencadenadores de Transact-SQL es que los desencadenadores de Transact-SQL pueden seguir funcionando más después de revertir la transacción.
Ejemplo
El siguiente desencadenador realiza una validación sencilla de instrucciones INSERT en una tabla. Si el valor entero insertado es igual a uno, la transacción se revierte y el valor no se inserta en la tabla. Todos los demás valores enteros se insertan en la tabla. Anote el bloque try/catch alrededor del Transaction.Rollback
método . El script de Transact-SQL crea una tabla de prueba, un ensamblado y un procedimiento almacenado administrado. Tenga en cuenta que las dos instrucciones INSERT se encapsulan en un bloque try/catch para que se detecte la excepción cuando el desencadenador finaliza la ejecución.
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;
Véase también
CREATE TRIGGER (Transact-SQL)
Desencadenadores DML
Desencadenadores DDL
PROBAR... CATCH (Transact-SQL)
Compilación de objetos de base de datos con la integración de Common Language Runtime (CLR)
EVENTDATA (Transact-SQL)