Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Karena integrasi SQL Server dengan runtime bahasa umum .NET Framework (CLR), Anda dapat menggunakan bahasa .NET Framework apa pun untuk membuat pemicu CLR. Bagian ini mencakup informasi khusus untuk pemicu yang diterapkan dengan integrasi CLR. Untuk diskusi lengkap tentang pemicu, lihat Pemicu DDL.
Apa Itu Pemicu?
Pemicu adalah jenis prosedur tersimpan khusus yang berjalan secara otomatis saat peristiwa bahasa dijalankan. SQL Server mencakup dua jenis pemicu umum: pemicu bahasa manipulasi data (DML) dan bahasa definisi data (DDL). Pemicu DML dapat digunakan saat INSERTpernyataan , , UPDATEatau DELETE memodifikasi data dalam tabel atau tampilan tertentu. DDL memicu prosedur tersimpan kebakaran sebagai respons terhadap berbagai pernyataan DDL, yang terutama merupakan pernyataan yang dimulai dengan CREATE, ALTER, dan DROP. Pemicu DDL dapat digunakan untuk tugas administratif, seperti mengaudit dan mengatur operasi database.
Kemampuan Unik Pemicu CLR
Pemicu yang ditulis dalam Transact-SQL memiliki kemampuan menentukan kolom mana dari tampilan atau tabel penembakan yang telah diperbarui dengan menggunakan UPDATE(column) fungsi dan COLUMNS_UPDATED() .
Pemicu yang ditulis dalam bahasa CLR berbeda dari objek integrasi CLR lainnya dengan beberapa cara yang signifikan. Pemicu CLR dapat:
Mereferensikan
INSERTEDdata dalam tabel danDELETEDMenentukan kolom mana yang telah dimodifikasi sebagai hasil dari
UPDATEoperasiAkses informasi tentang objek database yang terpengaruh oleh eksekusi pernyataan DDL.
Kemampuan ini disediakan secara inheren dalam bahasa kueri, atau oleh SqlTriggerContext kelas . Untuk informasi tentang keuntungan integrasi CLR dan memilih antara kode terkelola dan Transact-SQL, lihat Gambaran Umum Integrasi CLR.
Menggunakan Kelas SqlTriggerContext
Kelas SqlTriggerContext tidak dapat dibangun secara publik dan hanya dapat diperoleh dengan mengakses SqlContext.TriggerContext properti dalam isi pemicu CLR. Kelas SqlTriggerContext dapat diperoleh dari aktif SqlContext dengan memanggil SqlContext.TriggerContext properti:
SqlTriggerContext myTriggerContext = SqlContext.TriggerContext;
Kelas ini SqlTriggerContext menyediakan informasi konteks tentang pemicu. Informasi kontekstual ini mencakup jenis tindakan yang menyebabkan pemicu diaktifkan, kolom mana yang dimodifikasi dalam operasi UPDATE, dan, dalam kasus pemicu DDL, struktur XML EventData yang menjelaskan operasi pemicu. Untuk informasi selengkapnya, lihat EVENTDATA (Transact-SQL).
Menentukan Tindakan Pemicu
Setelah mendapatkan SqlTriggerContext, Anda dapat menggunakannya untuk menentukan jenis tindakan yang menyebabkan pemicu diaktifkan. Informasi ini tersedia melalui TriggerAction properti SqlTriggerContext kelas.
Untuk pemicu DML, TriggerAction properti bisa menjadi salah satu nilai berikut:
TriggerAction.Update (0x1)
TriggerAction.Insert (0x2)
TriggerAction.Delete(0x3)
Untuk pemicu DDL, daftar kemungkinan nilai TriggerAction jauh lebih lama. Silakan lihat "Enumerasi TriggerAction" di .NET Framework SDK untuk informasi selengkapnya.
Menggunakan Tabel yang Disisipkan dan Dihapus
Dua tabel khusus digunakan dalam pernyataan pemicu DML: tabel yang disisipkan dan tabel yang dihapus . SQL Server secara otomatis membuat dan mengelola tabel ini. Anda dapat menggunakan tabel sementara ini untuk menguji efek modifikasi data tertentu dan untuk mengatur kondisi untuk tindakan pemicu DML; namun, Anda tidak dapat mengubah data dalam tabel secara langsung.
Pemicu CLR dapat mengakses tabel yang disisipkan dan dihapus melalui penyedia dalam proses CLR. Ini dilakukan dengan mendapatkan SqlCommand objek dari objek SqlContext. Contohnya:
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"
Menentukan Kolom yang Diperbarui
Anda dapat menentukan jumlah kolom yang dimodifikasi oleh operasi UPDATE dengan menggunakan ColumnCount properti SqlTriggerContext objek. Anda dapat menggunakan IsUpdatedColumn metode , yang mengambil kolom ordinal sebagai parameter input, untuk menentukan apakah kolom diperbarui. Nilai True menunjukkan bahwa kolom telah diperbarui.
Misalnya, cuplikan kode ini (dari pemicu EmailAudit nanti dalam topik ini) mencantumkan semua kolom yang diperbarui:
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()
Mengakses EventData untuk Pemicu CLR DDL
Pemicu DDL, seperti pemicu reguler, prosedur tersimpan kebakaran sebagai respons terhadap suatu peristiwa. Tetapi tidak seperti pemicu DML, mereka tidak diaktifkan sebagai respons terhadap pernyataan UPDATE, INSERT, atau DELETE pada tabel atau tampilan. Sebaliknya, mereka menembak sebagai respons terhadap berbagai pernyataan DDL, yang terutama merupakan pernyataan yang dimulai dengan CREATE, ALTER, dan DROP. Pemicu DDL dapat digunakan untuk tugas administratif, seperti audit dan pemantauan operasi database dan perubahan skema.
Informasi tentang peristiwa yang mengaktifkan pemicu DDL tersedia di EventData properti SqlTriggerContext kelas. Properti ini berisi xml nilai. Skema xml menyertakan informasi tentang:
Waktu peristiwa.
ID Proses Sistem (SPID) koneksi di mana pemicu dijalankan.
Jenis peristiwa yang mengaktifkan pemicu.
Kemudian, tergantung pada jenis peristiwa, skema menyertakan informasi tambahan, seperti database tempat peristiwa terjadi, objek tempat peristiwa terjadi, dan perintah Transact-SQL peristiwa.
Dalam contoh berikut, pemicu DDL berikut mengembalikan properti mentah EventData .
Nota
Mengirim hasil dan pesan melalui SqlPipe objek ditampilkan di sini hanya untuk tujuan ilustrasi dan umumnya tidak dianjurkan untuk kode produksi saat memprogram pemicu CLR. Data tambahan yang dikembalikan mungkin tidak terduga dan menyebabkan kesalahan aplikasi.
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
Contoh output berikut adalah EventData nilai properti setelah pemicu DDL diaktifkan oleh CREATE TABLE peristiwa:
<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>
Selain informasi yang dapat diakses melalui SqlTriggerContext kelas, kueri masih dapat merujuk dan COLUMNS_UPDATED disisipkan/dihapus dalam teks perintah yang dijalankan dalam proses.
Contoh Pemicu CLR
Dalam contoh ini, pertimbangkan skenario di mana Anda membiarkan pengguna memilih ID apa pun yang mereka inginkan, tetapi Anda ingin mengetahui pengguna yang secara khusus memasukkan alamat email sebagai ID. Pemicu berikut akan mendeteksi informasi tersebut dan mencatatnya ke tabel audit.
Nota
Mengirim hasil dan pesan melalui SqlPipe objek ditampilkan di sini hanya untuk tujuan ilustrasi dan umumnya tidak dianjurkan untuk kode produksi. Data tambahan yang dikembalikan mungkin tidak terduga dan menyebabkan kesalahan aplikasi
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
Dengan asumsi ada dua tabel dengan definisi berikut:
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
)
Pernyataan Transact-SQL yang membuat pemicu di SQL Server adalah sebagai berikut, dan mengasumsikan rakitan SQLCLRTest sudah terdaftar dalam database SQL Server saat ini.
CREATE TRIGGER EmailAudit
ON Users
FOR INSERT, UPDATE, DELETE
AS
EXTERNAL NAME SQLCLRTest.CLRTriggers.EmailAudit
Memvalidasi dan Membatalkan Transaksi yang Tidak Valid
Menggunakan pemicu untuk memvalidasi dan membatalkan transaksi INSERT, UPDATE, atau DELETE yang tidak valid atau untuk mencegah perubahan pada skema database Anda adalah umum. Ini dapat dicapai dengan memasukkan logika validasi ke dalam pemicu Anda dan kemudian mengembalikan transaksi saat ini jika tindakan tidak memenuhi kriteria validasi.
Ketika dipanggil dalam pemicu, Transaction.Rollback metode atau SqlCommand dengan teks perintah "ROLLBACK TRANSAKSI" melemparkan pengecualian dengan pesan kesalahan ambigu dan harus dibungkus dalam blok coba/tangkap. Pesan kesalahan yang Anda lihat mirip dengan yang berikut ini:
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.
Pengecualian ini diharapkan dan blok try/catch diperlukan agar eksekusi kode berlanjut. Ketika kode pemicu selesai dieksekusi, pengecualian lain dinaikkan
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.
Pengecualian ini juga diharapkan, dan blok coba/tangkap di sekitar pernyataan Transact-SQL yang melakukan tindakan yang menembakkan pemicu diperlukan sehingga eksekusi dapat berlanjut. Meskipun dua pengecualian dilemparkan, transaksi digulung balik dan perubahan tidak diterapkan pada tabel. Perbedaan utama antara pemicu CLR dan pemicu Transact-SQL adalah bahwa pemicu Transact-SQL dapat terus melakukan lebih banyak pekerjaan setelah transaksi digulung balik.
Contoh
Pemicu berikut melakukan validasi sederhana pernyataan INSERT pada tabel. Jika nilai bilangan bulat yang disisipkan sama dengan satu, transaksi digulung balik dan nilai tidak dimasukkan ke dalam tabel. Semua nilai bilangan bulat lainnya disisipkan ke dalam tabel. Perhatikan blok coba/tangkap di Transaction.Rollback sekitar metode . Skrip Transact-SQL membuat tabel pengujian, perakitan, dan prosedur tersimpan terkelola. Perhatikan bahwa dua pernyataan INSERT dibungkus dalam blok coba/tangkap sehingga pengecualian dilemparkan ketika pemicu selesai eksekusi tertangkap.
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;
Lihat Juga
BUAT PEMICU (Transact-SQL)
Pemicu DML
Pemicu DDL
COBA... CATCH (Transact-SQL)
Membangun Objek Database dengan Integrasi Common Language Runtime (CLR)
EVENTDATA (Transact-SQL)