一、簡介 Microsoft SQL Server 2005之後,實現了對 Microsoft .NET Framework 的公共語言運行時(CLR)的集成。CLR 集成使得現在可以使用 .NET Framework 語言編寫代碼,從而能夠在 SQL Server 上運行,現在就可以通過 C# 來 ...
一、簡介
Microsoft SQL Server 2005之後,實現了對 Microsoft .NET Framework 的公共語言運行時(CLR)的集成。
CLR 集成使得現在可以使用 .NET Framework 語言編寫代碼,從而能夠在 SQL Server 上運行,現在就可以通過 C# 來編寫 SQL Server 自定義函數、存儲過程、觸發器等。
我最初的目的是因為在 SQL Server 資料庫中遇到數字的十進位與十六進位的互相轉換問題,也看過一些方法吧,但是最後我卻選擇了用 CLR 來做,畢竟在 C# 中兩三行代碼就能搞定的問題。。。
二、配置 SQL Server CLR
開啟 CLR:
--開啟所有伺服器配置 sp_configure 'show advanced options', 1; RECONFIGURE WITH override GO --開啟 CLR sp_configure 'clr enabled', 1; RECONFIGURE WITH override GO
關閉 CLR:
--關閉所有伺服器配置 sp_configure 'show advanced options', 0; RECONFIGURE WITH override GO --關閉 CLR sp_configure 'clr enabled', 0; RECONFIGURE WITH override GO
在後面註冊 CLR 程式集時,發生因操作許可權問題而導致的失敗時,可以嘗試執行下麵的 SQL 語句,這裡我把 SQL 一併貼出來。
--許可權不夠時,設置目標資料庫為可信賴的,例如:Test ALTER DATABASE [Test] SET TRUSTWORTHY ON --修改資料庫所有者為當前登錄的用戶,也可以為其他用戶,例如:sa EXEC sp_changedbowner 'sa'
三、CLR Function
打開 Visual Studio 新建一個 SQL Server 資料庫項目,這裡需要註意 .NET Framework 的版本。
因為我的目標資料庫為 SQL Server 2008,所以這裡我選擇的是 .NET Framework 3.5 的版本。
然後添加新建項,選擇 SQL CLR C# 用戶自定義函數,先從標量函數開始。
1、標量函數
public partial class UserDefinedFunctions { /// <summary> /// 10進位轉16進位 /// </summary> /// <param name="strNumber"></param> /// <returns></returns> [Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read, IsDeterministic = true, Name = "ConvertToHexadecimal")] public static SqlString ConvertToHexadecimal(SqlString strNumber) { SqlString result = string.Empty; string str = strNumber.ToString(); int number = 0; if (int.TryParse(str, out number)) { result = number.ToString("X"); } return result; } /// <summary> /// 16進位轉10進位 /// </summary> /// <param name="strNumber"></param> /// <returns></returns> [Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read, IsDeterministic = true, Name = "ConvertToDecimal")] public static SqlString ConvertToDecimal(SqlString strNumber) { SqlString result = string.Empty; string str = strNumber.ToString(); int number = 0; try { number = int.Parse(str, System.Globalization.NumberStyles.HexNumber); result = Convert.ToString(number, 10); } catch { } return result; } }
2、表值函數
public partial class UserDefinedFunctions { /// <summary> /// SQL Server 字元串分割方法 /// </summary> /// <param name="separator"></param> /// <param name="pendingString"></param> /// <returns></returns> [Microsoft.SqlServer.Server.SqlFunction( DataAccess = DataAccessKind.Read, IsDeterministic = true, Name = "SqlSplit", FillRowMethodName = "SqlSplit_FillRow", TableDefinition = "SerialNumber int,StringValue nvarchar(1024)")] public static IEnumerable SqlSplit(SqlString separator, SqlString pendingString) { string _separator = string.Empty; string _pendingString = string.Empty; if (separator.IsNull) { _separator = ","; } else { _separator = separator.ToString(); if (string.IsNullOrEmpty(_separator)) { _separator = ","; } } if (pendingString.IsNull) { return null; } else { _pendingString = pendingString.ToString(); if (string.IsNullOrEmpty(_pendingString)) { return null; } } string[] strs = _pendingString.Split(new string[] { _separator }, StringSplitOptions.RemoveEmptyEntries); if (strs.Length <= 0) { return null; } List<ResultData> resultDataList = new List<ResultData>(); for (int i = 0; i < strs.Length; i++) { resultDataList.Add(new ResultData(i + 1, strs[i])); } return resultDataList; } /// <summary> /// 填充數據方法 /// </summary> /// <param name="obj"></param> /// <param name="serialNumber"></param> /// <param name="stringValue"></param> public static void SqlSplit_FillRow(Object obj, out SqlInt32 SerialNumber, out SqlString StringValue) { ResultData resultData = (ResultData)obj; SerialNumber = resultData.SerialNumber; StringValue = resultData.StringValue; } /// <summary> /// 定義返回類型 /// </summary> public class ResultData { /// <summary> /// 序號,即行號 /// </summary> public SqlInt32 SerialNumber { get; set; } /// <summary> /// 分割後的每個子字元串 /// </summary> public SqlString StringValue { get; set; } public ResultData(SqlInt32 serialNumber, SqlString stringValue) { SerialNumber = serialNumber; StringValue = stringValue; } } }
SqlFunctionAttribute 的屬性及介紹:
--屬性 --說明 --DataAccess --指示該函數是否涉及訪問存儲在SQL Server的數據 --FillRowMethodName --在同一個類的方法的名稱作為表值函數(TVF),這個參數在表值函數中才會用到,用於指定表值函數的數據填充方法 --IsDeterministic --指示用戶定義的函數是否是確定性的 --IsPrecise --指示函數是否涉及不精確計算,如浮點運算 --Name --函數在SQL Server中註冊時使用的函數的名稱 --SystemDataAccess --指示該函數是否需要訪問存儲在系統目錄或SQL Server虛擬系統表中的數據 --TableDefinition --如果方法作為表值函數(TVF),則為一個字元串,該字元串表示表結構的定義
標量函數與表值函數可以寫在同一個類文件裡面,並且可以包含多個,但是聚合函數就不行了,現在需要添加一個新項,選擇 SQL CLR C# 聚合。
3、聚合函數
我這裡寫的這個聚合函數的作用是把多個字元串拼為一個字元串,我之前還真有遇到這種情況需要的。
[Serializable] [Microsoft.SqlServer.Server.SqlUserDefinedAggregate( Format.UserDefined, IsInvariantToDuplicates = false, IsInvariantToNulls = true, IsInvariantToOrder = false, MaxByteSize = 8000, Name = "SumString")] public struct UserDefinedSqlAggregate : IBinarySerialize { private StringBuilder stringBuilder; /// <summary> /// 查詢處理器使用此方法初始化聚合的計算 /// </summary> public void Init() { stringBuilder = new StringBuilder(); } /// <summary> /// 查詢處理器使用此方法累計聚合值 /// </summary> /// <param name="Value"></param> public void Accumulate(SqlString Value) { stringBuilder.Append(string.Format("{0},", Value)); } /// <summary> /// 查詢處理器使用此方法合併聚合的多個部分計算的值 /// </summary> /// <param name="Group"></param> public void Merge(UserDefinedSqlAggregate Group) { stringBuilder.Append(Group.stringBuilder); } /// <summary> /// 此方法用於返回完成聚合計算的結果 /// </summary> /// <returns></returns> public SqlString Terminate() { return new SqlString(stringBuilder.ToString()); } #region Implement interface IBinarySerialize /// <summary> /// 讀 /// </summary> /// <param name="r"></param> public void Read(System.IO.BinaryReader r) { stringBuilder = new StringBuilder(r.ReadString()); } /// <summary> /// 寫 /// </summary> /// <param name="w"></param> public void Write(System.IO.BinaryWriter w) { w.Write(stringBuilder.ToString()); } #endregion }
SqlUserDefinedAggregateAttribute 的屬性及介紹:
--屬性 --說明 --Format --選擇序列化的 Format 格式,預設選擇 Native,表示使用本地序列化格式。如果選擇 UserDefined,則聚合類需要實現 IBinarySerialize 介面 --IsInvariantToDuplicates --指示聚合是否與重覆的值相計算保持不變 --IsInvariantToNulls --指示聚合是否與空值相計算保持不變 --IsInvariantToOrder --指示聚合最後計算的結果是否與順序無關 --IsNullIfEmpty --指示在沒有對任何值進行累計時,聚合返回值是否為 null --MaxByteSize --聚合實例的最大大小(以位元組為單位) --Name --聚合函數的名稱
然後生成項目,接下來註冊程式集和註冊函數就可以使用了。
4、註冊 CLR 程式集
註冊程式集的方式有以下兩種:
第一種,這種方式註冊程式集比較簡單,但是缺點就是程式集不能移動或刪除。
--註冊CLR程式集方式一,指定程式集DLL的路徑 USE Test GO CREATE ASSEMBLY UserDefinedClrAssembly --AUTHORIZATION sa --指定資料庫所有者,預設為當前用戶 FROM 'C:\Users\Administrator\Desktop\CLR Assembly\UserDefinedSqlClr.dll' --指定文件路徑 WITH PERMISSION_SET = UNSAFE; --指定程式集的許可權 --SAFE:無法訪問外部系統資源; --EXTERNAL_ACCESS:可以訪問某些外部系統資源; --UNSAFE:可以不受限制的訪問外部系統資源 GO
這裡如果發生因為程式集拒絕訪問的錯誤,那就把電腦用戶 Everyone 的許可權改為完全控制就可以了。
第二種,這種方式註冊程式集稍微複雜一些,但是好處就是註冊成功之後,可以移動甚至刪除DLL文件,只要不是變更遷移資料庫,都不用重新註冊。
--註冊CLR程式集方式二,指定程式集DLL的16進位文件流 USE Test GO CREATE ASSEMBLY UserDefinedClrAssembly --AUTHORIZATION sa --指定資料庫所有者,預設為當前用戶 FROM 0x4D5A90000300000004000000FFFF0000B8000000000000004000000000 --指定DLL的16進位文件流(當然沒這麼少,我刪掉了) WITH PERMISSION_SET = UNSAFE; --指定程式集的許可權 --SAFE:無法訪問外部系統資源; --EXTERNAL_ACCESS:可以訪問某些外部系統資源; --UNSAFE:可以不受限制的訪問外部系統資源 GO
獲取DLL的16進位文件流,可以使用 UltraEdit 這個軟體,具體操作方法這裡就不多說了。
註冊成功之後,可以使用下麵的 SQL 語句查看程式集的信息,還包括查詢自定義的函數、存儲過程等的SQL語句,這個下麵註冊函數之後可以用到。
--查看程式集信息 SELECT * FROM sys.assemblies --查看模塊信息,即自定義函數、視圖、存儲過程、觸發器等等 SELECT * FROM sys.sql_modules GO
5、註冊函數
下麵是三種函數的註冊方式的 SQL 語句。
USE Test GO --註冊標量函數 ConvertToHexadecimal CREATE FUNCTION [dbo].[ConvertToHexadecimal](@strNumber NVARCHAR(128)) RETURNS NVARCHAR(128) WITH EXECUTE AS CALLER --用於在用戶在執行函數的時候對引用的對象進行許可權檢查 AS EXTERNAL NAME [UserDefinedClrAssembly].[UserDefinedFunctions].[ConvertToHexadecimal] --EXTERNAL NAME 程式集名.類名.方法名 GO --註冊標量函數 ConvertToDecimal CREATE FUNCTION [dbo].[ConvertToDecimal](@strNumber NVARCHAR(128)) RETURNS NVARCHAR(128) WITH EXECUTE AS CALLER --用於在用戶在執行函數的時候對引用的對象進行許可權檢查 AS EXTERNAL NAME [UserDefinedClrAssembly].[UserDefinedFunctions].[ConvertToDecimal] --EXTERNAL NAME 程式集名.類名.方法名 GO --註冊表值函數 SqlSplit CREATE FUNCTION [dbo].[SqlSplit](@separator NVARCHAR(32),@string NVARCHAR(MAX)) RETURNS TABLE ( SerialNumber INT, StringValue NVARCHAR(1024) ) WITH EXECUTE AS CALLER --用於在用戶在執行函數的時候對引用的對象進行許可權檢查 AS EXTERNAL NAME [UserDefinedClrAssembly].[UserDefinedFunctions].[SqlSplit] --EXTERNAL NAME 程式集名.類名.方法名 GO --註冊聚合函數 SumString CREATE AGGREGATE [dbo].[SumString](@params NVARCHAR(128)) RETURNS NVARCHAR(MAX) EXTERNAL NAME [UserDefinedClrAssembly].[UserDefinedSqlAggregate] --EXTERNAL NAME 程式集名.類名 GO
註冊函數成功之後,接下來測試一下。
DECLARE @TempTable TABLE ( Id INT NOT NULL, Name NVARCHAR(32) NOT NULL ) INSERT INTO @TempTable ( Id, [Name] ) SELECT '1','小張' UNION ALL SELECT '2','小明' UNION ALL SELECT '2','小麗' UNION ALL SELECT '2','小李' UNION ALL SELECT '3','小王' UNION ALL SELECT '3','小舞' SELECT dbo.ConvertToHexadecimal('15') SELECT dbo.ConvertToDecimal('FC') SELECT * FROM SqlSplit(',',',123,456,789,') SELECT Id,dbo.SumString([Name]) Names FROM @TempTable GROUP BY Id
結果如圖。
下麵是刪除函數和刪除程式集的 SQL 語句,雖然可能用不到,但是還是貼出來吧。
這裡需要註意的是,刪除程式集時要保證不存在函數、存儲過程、觸發器等對程式集的引用。
--刪除標量函數 ConvertToHexadecimal DROP FUNCTION dbo.ConvertToHexadecimal --刪除標量函數 ConvertToDecimal DROP FUNCTION dbo.ConvertToDecimal --刪除表值函數 SqlSplit DROP FUNCTION dbo.SqlSplit --刪除聚合函數 SumString DROP FUNCTION dbo.SumString --刪除程式集 UserDefinedClrAssembly DROP ASSEMBLY UserDefinedClrAssembly
本想一篇寫完的,還是算了,存儲過程和觸發器留待下一篇。
其實存儲過程和觸發器也沒什麼了,只是 C# 代碼不一樣而已,其他註冊之類的大同小異。
這裡推薦一篇博客,大家也可以去看這篇,寫得還是挺完整的,有些地方都是借鑒於此。
http://blog.csdn.net/tjvictor/article/details/4726933