SQL Server数据库Table Data Audit

对于重要的数据库表,特别是关于权限的,我们在生产机环境都要存储Audit信息,下面我们先生成一个Audit Table:

CREATE TABLE [dbo].[DATA_AUDIT](
	[ROW_ID] [uniqueidentifier] NOT NULL,
	[LST_CHG_TMS] [datetime] NOT NULL,
	[LST_CHG_USR_ID] [nvarchar](12) NOT NULL,
	[TARGET_TABLE] [nvarchar](128) NOT NULL,
	[ACTION] [nvarchar](50) NULL,
	[RECORD_KEY] [nvarchar](200) NULL,
	[OLD_ROW_DETAILS] [varchar](MAX) NULL,
	[NEW_ROW_DETAILS] [varchar](MAX) NULL
)
有了这张表,对于每一个需要Audit的表,我们需要添加一个Trigger,这个过程比较枯燥,可以通过下面的T4 Template在Visual Studio中实现:

<#@ template debug="false" hostspecific="false" #>
<#@ output extension=".sql" #>
<#@ assembly name="System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"  #>
<#@ assembly name="System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"  #>
<#@ assembly name="System.Transactions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"  #>
<#@ import namespace="System" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Transactions" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#	var databaseName = "";
	var schemaName = "dbo";
	var tableName = "";

	var connectionString = "";
#>
<#
	var	triggerName = "TRG_" + tableName;
#>
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
SET ANSI_WARNINGS ON
GO
SET ARITHABORT ON
GO
SET CONCAT_NULL_YIELDS_NULL ON
GO
SET NUMERIC_ROUNDABORT OFF
GO

USE <#= databaseName #> 
GO

IF NOT EXISTS
(
	SELECT 1
	FROM SYS.Triggers tr
	INNER JOIN SYS.Tables t
	ON tr.parent_id = t.object_id
	WHERE t.name = '<#= tableName #>'
	AND tr.name = '<#= triggerName #>'
)
BEGIN
	DECLARE @sql NVARCHAR(MAX) = '
	CREATE TRIGGER <#= schemaName #>.<#= triggerName #> ON <#= schemaName #>.<#= tableName #>
	FOR DELETE, INSERT, UPDATE
	NOT FOR REPLICATION
	AS
	BEGIN
		SELECT 1
	END'
	EXEC (@sql)
END
GO

ALTER TRIGGER <#= schemaName #>.<#= triggerName #> ON <#= schemaName #>.<#= tableName #>
FOR DELETE, INSERT, UPDATE
NOT FOR REPLICATION
AS
BEGIN
	DECLARE @USER_ID CHAR(8) = 'mcai4gl2',
			@CURR_TIME DATETIME = GETDATE(),
			@COUNT_OLD INT,
			@COUNT_NEW INT

	SELECT @COUNT_OLD = COUNT(1) FROM DELETED
	SELECT @COUNT_NEW = COUNT(1) FROM INSERTED
	<#
		var columns = new Dictionary<String, int>();
		
		using (var conn = new SqlConnection(connectionString))
		{
			conn.Open();

			var cmd = new SqlCommand(
				String.Format(
				@"
					select DISTINCT c.column_name, 
					CASE WHEN OBJECTPROPERTY(OBJECT_ID(k.constraint_name), 'IsPrimaryKey') = 1 THEN 1 ELSE 0 END AS IsPrimary,
					c.ORDINAL_POSITION
					from information_schema.columns c
					LEFT join INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
					on c.TABLE_NAME = k.TABLE_NAME AND c.COLUMN_NAME = k.COLUMN_NAME
					where c.table_name = '{0}'
					order by c.ORDINAL_POSITION
				", tableName),
				conn);

			using (var reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
					columns.Add(reader.GetString(0), reader.GetInt32(1));
                }
            }
		}

		var keyStrs = (from column in columns where column.Value == 1 select "'<" + column.Key.Trim() + ">' + " 
					  + "ISNULL(CONVERT(VARCHAR(MAX), {0}" + column.Key.Trim() + "), '') + '</" + column.Key.Trim() + ">").ToList();
		var valueStrs = (from column in columns where column.Value == 0 select "'<" + column.Key.Trim() + ">' + " 
					  + "ISNULL(CONVERT(VARCHAR(MAX), {0}" + column.Key.Trim() + "), '') + '</" + column.Key.Trim() + ">").ToList();
	#>

	-- UPDATE
	IF @COUNT_NEW > 0 AND @COUNT_OLD > 0
	BEGIN
		INSERT INTO <#= schemaName #>.DATA_AUDIT
		(
			LST_CHG_TMS,
			LST_CHG_USR_ID,
			TARGET_TABLE,
			ACTION,
			RECORD_KEY,
			OLD_ROW_DETAILS,
			NEW_ROW_DETAILS
		)
		SELECT
			@CURR_TIME,
			RTRIM(@USER_ID),
			'<#= tableName #>',
			'Update',
			ISNULL(RES.KEY_VAL,'UNKNOWN'),
			ISNULL(RES.OLD_XML_OUT,''),
			ISNULL(RES.NEW_XML_OUT,'')	
			FROM
			(
				SELECT
<# 
	for (int index = 0; index < valueStrs.Count(); index++) {
		var line = valueStrs[index];
		if (index < valueStrs.Count() - 1)
		{
			line += "|' + ";
		}
		else
		{
			line += "'";
		}
#>
					<#= String.Format(line, "d.") #>
<#
	}
#>
					AS OLD_XML_OUT,
<# 
	for (int index = 0; index < valueStrs.Count(); index++) {
		var line = valueStrs[index];
		if (index < valueStrs.Count() - 1)
		{
			line += "|' + ";
		}
		else
		{
			line += "'";
		}
#>
					<#= String.Format(line, "i.") #>
<#
	}
#>
					AS NEW_XML_OUT,
<# 
	for (int index = 0; index < keyStrs.Count(); index++) {
		var line = keyStrs[index];
		if (index < keyStrs.Count() - 1)
		{
			line += "|' + ";
		}
		else
		{
			line += "'";
		}
#>
					<#= String.Format(line, "i.") #>
<#
	}
#>
					AS KEY_VAL
				FROM 
					DELETED d
					INNER JOIN INSERTED i ON
<# 
	var keys = (from column in columns where column.Value == 1 select column.Key.Trim()).ToList();
	for (int index = 0; index < keys.Count(); index++) {
		var line = "i." + keys[index] + " = d." + keys[index];
		if (index < keys.Count() - 1)
		{
			line += " AND";
		}
#>
					<#= line #>
<#
	}
#>
			) RES
			WHERE RES.NEW_XML_OUT <> RES.OLD_XML_OUT
	END
	-- INSERT
	ELSE IF @COUNT_NEW > 0 AND @COUNT_OLD = 0
	BEGIN
			INSERT INTO <#= schemaName #>.DATA_AUDIT
		(
			LST_CHG_TMS,
			LST_CHG_USR_ID,
			TARGET_TABLE,
			ACTION,
			RECORD_KEY,
			OLD_ROW_DETAILS,
			NEW_ROW_DETAILS
		)
		SELECT
			@CURR_TIME,
			RTRIM(@USER_ID),
			'<#= tableName #>',
			'Insert',
			ISNULL(RES.KEY_VAL,'UNKNOWN'),
			'',
			ISNULL(RES.NEW_XML_OUT,'')
			FROM
			(
				SELECT
<# 
	for (int index = 0; index < valueStrs.Count(); index++) {
		var line = valueStrs[index];
		if (index < valueStrs.Count() - 1)
		{
			line += "|' + ";
		}
		else
		{
			line += "'";
		}
#>
					<#= String.Format(line, "i.") #>
<#
	}
#>
					AS NEW_XML_OUT,
<# 
	for (int index = 0; index < keyStrs.Count(); index++) {
		var line = keyStrs[index];
		if (index < keyStrs.Count() - 1)
		{
			line += "|' + ";
		}
		else
		{
			line += "'";
		}
#>
					<#= String.Format(line, "i.") #>
<#
	}
#>
					AS KEY_VAL
				FROM 
					INSERTED i
			) RES
	END
	-- DELETE
	ELSE IF @COUNT_NEW = 0 AND @COUNT_OLD > 0
	BEGIN
		INSERT INTO <#= schemaName #>.DATA_AUDIT
		(
			LST_CHG_TMS,
			LST_CHG_USR_ID,
			TARGET_TABLE,
			ACTION,
			RECORD_KEY,
			OLD_ROW_DETAILS,
			NEW_ROW_DETAILS
		)
		SELECT
			@CURR_TIME,
			RTRIM(@USER_ID),
			'<#= tableName #>',
			'Delete',
			ISNULL(RES.KEY_VAL,'UNKNOWN'),
			ISNULL(RES.OLD_XML_OUT,''),
			''
			FROM
			(
				SELECT
<# 
	for (int index = 0; index < valueStrs.Count(); index++) {
		var line = valueStrs[index];
		if (index < valueStrs.Count() - 1)
		{
			line += "|' + ";
		}
		else
		{
			line += "'";
		}
#>
					<#= String.Format(line, "d.") #>
<#
	}
#>
					AS OLD_XML_OUT,
<# 
	for (int index = 0; index < keyStrs.Count(); index++) {
		var line = keyStrs[index];
		if (index < keyStrs.Count() - 1)
		{
			line += "|' + ";
		}
		else
		{
			line += "'";
		}
#>
					<#= String.Format(line, "d.") #>
<#
	}
#>
					AS KEY_VAL
				FROM 
					DELETED d
			) RES
	END
END
GO

SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

通过上面的template,我们只需要提供数据库Connection String和需要Audit的表的名字,Trigger就可以自动生成了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值