时间:2021-07-01 10:21:17 帮助过:19人阅读
--开启所有服务器配置 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
SQL Server CLR 使用 C# 自定义函数
标签:.dll 测试 开始 lsp -- rust details binary not