我做了一些测试,我自己解决了这个问题,这里是为了分享整个故事;-)
1) .NET SQL CLR 触发器,只有 1 个触发器将监听两个表,唯一的假设是被监视的表有一个名为Id的标识列
using System.Data.SqlClient;
using System.Xml;
using Microsoft.SqlServer.Server;
namespace Axis.CLR.SampleObjects
{
using System;
using System.Data;
using System.Data.SqlTypes;
using System.Text;
public partial class AuditTrigger
{
public const string GetTableContextStatement =
"SELECT object_name(resource_associated_entity_id) FROM sys.dm_tran_locks WHERE request_session_id = @@spid and resource_type = 'OBJECT'";
[SqlTrigger(Name = "UserNameAudit", Target = "Users", Event = "FOR INSERT")]
public static void UserNameAudit()
{
SqlTriggerContext triggContext = SqlContext.TriggerContext;
//SqlPipe sqlP = SqlContext.Pipe;
using (SqlConnection conn = new SqlConnection("context connection=true"))
using (SqlCommand sqlComm = conn.CreateCommand())
{
conn.Open();
// Gets a reference to the affected table name
string tableName = string.Empty;
using (SqlCommand cmd = new SqlCommand(GetTableContextStatement, conn))
{
tableName = cmd.ExecuteScalar().ToString();
}
// STORING INSERT AUDIT
if (triggContext.TriggerAction == TriggerAction.Insert)
{
#region handling INSERT action
sqlComm.CommandText = "SELECT * from INSERTED";
var reader = sqlComm.ExecuteReader();
if (reader.Read())
{
XmlDocument finalDocument = new XmlDocument();
XmlNode rootElement = finalDocument.CreateNode(XmlNodeType.Element, tableName, string.Empty);
XmlAttribute newAttribute = finalDocument.CreateAttribute("Id");
newAttribute.Value = reader.GetInt64(reader.GetOrdinal("Id")).ToString();
rootElement.Attributes.Append(newAttribute);
newAttribute = finalDocument.CreateAttribute("Operation");
newAttribute.Value = "INSERT";
rootElement.Attributes.Append(newAttribute);
finalDocument.AppendChild(rootElement);
XmlNode createdElement = finalDocument.CreateNode(XmlNodeType.Element, "Fields", string.Empty);
for (int i = 0; i < reader.FieldCount; i++)
{
XmlNode fieldElement = finalDocument.CreateNode(XmlNodeType.Element, reader.GetName(i), string.Empty);
if (reader.IsDBNull(i))
{
fieldElement.InnerText = "NULL";
}
else
{
fieldElement.InnerText = reader.GetValue(i).ToString();
}
createdElement.AppendChild(fieldElement);
}
// Node was added
rootElement.AppendChild(createdElement);
// Adds the Audit
sqlComm.CommandText = "[dbo].[AddAuditTrail]";
sqlComm.CommandType = CommandType.StoredProcedure;
SqlParameter xmlParamA = new SqlParameter("@ObjectId", SqlDbType.BigInt);
xmlParamA.Value = reader.GetInt64(reader.GetOrdinal("Id"));
sqlComm.Parameters.Add(xmlParamA);
reader.Close();
sqlComm.Parameters.AddWithValue("@ObjectName", tableName);
SqlParameter xmlParamB = new SqlParameter("@TraceXML", SqlDbType.Xml);
xmlParamB.Value = new SqlXml(new XmlTextReader(finalDocument.OuterXml, XmlNodeType.Document, null));
sqlComm.Parameters.Add(xmlParamB);
sqlComm.Parameters.AddWithValue("@AuditType", "INSERT");
sqlComm.ExecuteNonQuery();
//sqlP.Send(string.Format("Generated AFTER INSERT XML is: '{0}'", finalDocument.OuterXml));
}
#endregion handling INSERT action
}
else if (triggContext.TriggerAction == TriggerAction.Update)
{
#region handling UPDATE action
DataSet values = new DataSet();
SqlDataAdapter adapter = new SqlDataAdapter(sqlComm);
sqlComm.CommandText = "SELECT * from INSERTED";
adapter.Fill(values, "INSERTED");
sqlComm.CommandText = "SELECT * from DELETED";
adapter.Fill(values, "DELETED");
StringBuilder builder = new StringBuilder();
builder.Append("<Fields>");
int recordId = 0;
for (int i = 0; i < values.Tables["INSERTED"].Columns.Count; i++)
{
string colName = values.Tables["INSERTED"].Columns[i].ColumnName;
if (colName.ToLower().Equals("id"))
{
recordId = Convert.ToInt32(values.Tables["DELETED"].Rows[0][i]);
builder.AppendFormat("<Id value='{0}' />", recordId);
}
// if both nulls or both the same, no audit needed...
if (values.Tables["INSERTED"].Rows[0].IsNull(i) && values.Tables["DELETED"].Rows[0].IsNull(i))
{
continue;
}
if (values.Tables["INSERTED"].Rows[0][i].Equals(values.Tables["DELETED"].Rows[0][i]))
{
continue;
}
builder.AppendFormat("<{0}>", colName);
// DUMPING OLD VALUE
builder.Append("<OldValue>");
if (values.Tables["DELETED"].Rows[0].IsNull(i))
{
builder.Append("NULL");
}
else
{
builder.Append(values.Tables["DELETED"].Rows[0][i]);
}
builder.Append("</OldValue>");
// DUMPING NEW VALUE
builder.Append("<NewValue>");
if (values.Tables["INSERTED"].Rows[0].IsNull(i))
{
builder.Append("NULL");
}
else
{
builder.Append(values.Tables["INSERTED"].Rows[0][i]);
}
builder.Append("</NewValue>");
builder.AppendFormat("</{0}>", colName);
}
builder.Append("</Fields>");
builder.Insert(0, string.Format("<{0} Id='{1}' Operation='{2}'>", tableName, recordId, "UPDATE"));
builder.AppendFormat("</{0}>", tableName);
// Adds the Audit
sqlComm.CommandText = "[dbo].[AddAuditTrail]";
sqlComm.CommandType = CommandType.StoredProcedure;
SqlParameter xmlParamA = new SqlParameter("@ObjectId", SqlDbType.BigInt);
xmlParamA.Value = recordId;
sqlComm.Parameters.Add(xmlParamA);
sqlComm.Parameters.AddWithValue("@ObjectName", tableName);
SqlParameter xmlParamB = new SqlParameter("@TraceXML", SqlDbType.Xml);
xmlParamB.Value = new SqlXml(new XmlTextReader(builder.ToString(), XmlNodeType.Document, null));
sqlComm.Parameters.Add(xmlParamB);
sqlComm.Parameters.AddWithValue("@AuditType", "UPDATE");
sqlComm.ExecuteNonQuery();
//sqlP.Send(string.Format("Generated AFTER UPDATE XML is: '{0}'", builder.ToString()));
#endregion handling UPDATE action
}
}
}
}
}
2)这里的 SQL 代码我用来在 sql server 上注册触发器并将其链接到两个不同的表(用户和产品),只有 1 个 CLR 触发器,但在 SQL server 中,两个触发器使用 CLR 一个创建为外部, 每个表都有一个
USE [Axis_Davide]
GO
BEGIN TRANSACTION SCRIPT
---------------------------------
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'trAuditTriggerA') AND type in (N'TA'))
BEGIN
DROP TRIGGER [dbo].[trAuditTriggerA]
PRINT('Trigger A was removed');
END
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'trAuditTriggerB') AND type in (N'TA'))
BEGIN
DROP TRIGGER [dbo].[trAuditTriggerB]
PRINT('Trigger B was removed');
END
IF EXISTS (SELECT * FROM sys.assemblies asms WHERE asms.name = N'Axis.CLR.SampleObjects' and is_user_defined = 1)
BEGIN
DROP ASSEMBLY [Axis.CLR.SampleObjects]
PRINT('Assembly was removed');
END
CREATE ASSEMBLY [Axis.CLR.SampleObjects]
AUTHORIZATION [dbo]
FROM 'C:\Axis\SQLCLR_Samples\Axis.CLR.SampleObjects.dll'
WITH PERMISSION_SET = SAFE
PRINT('Assembly was created');
EXEC('CREATE TRIGGER trAuditTriggerA ON [dbo].[Users] AFTER INSERT, UPDATE AS EXTERNAL NAME [Axis.CLR.SampleObjects].[Axis.CLR.SampleObjects.AuditTrigger].[UserNameAudit]')
PRINT('Trigger A was created');
EXEC('CREATE TRIGGER trAuditTriggerB ON [dbo].[Products] AFTER INSERT, UPDATE AS EXTERNAL NAME [Axis.CLR.SampleObjects].[Axis.CLR.SampleObjects.AuditTrigger].[UserNameAudit]')
PRINT('Trigger B was created');
---------------------------------
COMMIT TRANSACTION SCRIPT
3)这里是我的审计表的创建语句
CREATE TABLE [dbo].[AuditTrail]
(
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[AuditDate] [datetime2](7) NOT NULL,
[UserName] [nvarchar](64) NOT NULL,
[ObjectId] [bigint] NOT NULL,
[ObjectName] [nvarchar](128) NOT NULL,
[TraceXML] [xml] NOT NULL,
[TraceSize] [int] NOT NULL,
[AuditType] [nvarchar](16) NOT NULL,
CONSTRAINT [PK_AuditTrail] PRIMARY KEY CLUSTERED ( [Id] ASC )
)
GO
ALTER TABLE [dbo].[AuditTrail] ADD CONSTRAINT [DF_AuditTrail_AuditDate] DEFAULT (sysutcdatetime()) FOR [AuditDate]
GO
ALTER TABLE [dbo].[AuditTrail] ADD CONSTRAINT [DF_AuditTrail_UserName] DEFAULT (suser_sname()) FOR [UserName]
GO
4)这里由触发器调用的存储过程在每次插入/更新时添加新的审计记录
CREATE PROCEDURE [dbo].[AddAuditTrail]
@ObjectId bigint,
@ObjectName nvarchar(128),
@TraceXML xml,
@AuditType nvarchar(16)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
INSERT INTO [dbo].[AuditTrail] ([ObjectId], [ObjectName], [TraceXML], [TraceSize], [AuditType])
VALUES (@ObjectId, @ObjectName, @TraceXML, DATALENGTH(@TraceXML), @AuditType)
END
GO
5)我的审计表的内容如下所示,对于一个INSERT
和对于一个UPDATE
<Users Id="51" Operation="INSERT">
<Fields>
<UserName>Davide</UserName>
<Pass>Test</Pass>
<Id>51</Id>
<Email>NULL</Email>
</Fields>
</Users>
<Users Id="51" Operation="UPDATE">
<Fields>
<Id value="51" />
<Email>
<OldValue>NULL</OldValue>
<NewValue>@</NewValue>
</Email>
</Fields>
</Users>