一、简介

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

转载自:http://www.cnblogs.com/Brambling/p/8000911.html

SQL Server CLR 使用 C# 自定义函数的更多相关文章

  1. SQL Server CLR 使用 C# 自定义存储过程和触发器

    资源来源:https://www.cnblogs.com/Brambling/p/8016060.html SQL Server CLR 使用 C# 自定义存储过程和触发器   这一篇博客接着上一篇博 ...

  2. SQL Server技术问题之自定义函数优缺点

    优点: 可以在SQL语句中调用,直接使用返回值,从而可以形成复杂的SQL应用. 缺点: 能在函数中使用的语句有严格限制: 不支持create.ALTER.drop等DDL(Data Definitio ...

  3. SQL SERVER中用户定义标量函数(scalar user defined function)的性能问题

    用户定义函数(UDF)分类  SQL SERVER中的用户定义函数(User Defined Functions 简称UDF)分为标量函数(Scalar-Valued Function)和表值函数(T ...

  4. SQL Server利用RowNumber()内置函数与Over关键字实现通用分页存储过程(支持单表或多表结查集分页)

    SQL Server利用RowNumber()内置函数与Over关键字实现通用分页存储过程,支持单表或多表结查集分页,存储过程如下: /******************/ --Author:梦在旅 ...

  5. (转载)MS SQL Server 未公开的加密函数有哪些?

    MS SQL Server 未公开的加密函数有哪些? 以下的文章是对MS SQL Server 未公开的加密函数的具体操作,如果你对其相关的实际操作有兴趣的话,你就可以点击了. MS SQL Serv ...

  6. SQL SERVER 提供了一些时间函数:

    SQL SERVER 提供了一些时间函数:取当前时间:select getdate()取前一个月的时间:SELECT DATEADD(MONTH,-1,GETDATE()) 月份减一个月取年份:SEL ...

  7. 深入理解SQL Server 2005 中的 COLUMNS_UPDATED函数

    原文:深入理解SQL Server 2005 中的 COLUMNS_UPDATED函数 概述 COLUMNS_UPDATED函数能够出现在INSERT或UPDATE触发器中AS关键字后的任何位置,用来 ...

  8. SQL Server 2019 中标量用户定义函数性能的改进

    在SQL Server中,我们通常使用用户定义的函数来编写SQL查询.UDF接受参数并将结果作为输出返回.我们可以在编程代码中使用这些UDF,并且可以快速编写查询.我们可以独立于任何其他编程代码来修改 ...

  9. SQL Server CLR全功略之一---CLR介绍和配置

    Microsoft SQL Server 现在具备与 Microsoft Windows .NET Framework 的公共语言运行时 (CLR) 组件集成的功能.CLR 为托管代码提供服务,例如跨 ...

随机推荐

  1. [Windows Azure] How to Monitor Cloud Services

    How to Monitor Cloud Services To use this feature and other new Windows Azure capabilities, sign up ...

  2. 每日英语:Welcome to the Global Middle-Class Surge

    The mass uprisings this summer in Egypt, Turkey and Brazil are powerful reminders that the middle cl ...

  3. Custom Sublime Text Build Systems For Popular Tools And Languages

    Sublime Text is currently the text editor of choice for a number of developers in the open-source co ...

  4. Mac下用brew搭建PHP(LNMP/LAMP)开发环境

    Mac下搭建lamp开发环境很容易,有xampp和mamp现成的集成环境.但是集成环境对于经常需要自定义一些配置的开发者来说会非常麻烦,而且Mac本身自带apache和php,在brew的帮助下非常容 ...

  5. (转)C++11里的智能指针

    1. std::auto_ptr有些违背c++编程思想. 已经被"不建议使用了".2. 下文转自:http://blog.csdn.net/lanergaming/article/ ...

  6. TensorFlow学习笔记(8)--网络模型的保存和读取【转】

    转自:http://blog.csdn.net/lwplwf/article/details/62419087 之前的笔记里实现了softmax回归分类.简单的含有一个隐层的神经网络.卷积神经网络等等 ...

  7. Mac上把python源文件编译成so文件

    把python源文件编译成so文件 前言 实际上属于一种代码混淆/加密的技术,大家知道python的源文件放在那里,大家是都可以看的,不像C语言编译出来可以拿编译后的东西去运行,所以就出现了这种需求. ...

  8. 用adb命令组装PowerShell实用小工具——Android测试小助手

    [本文出自天外归云的博客园] 简介 APP性能测试一般对以下几个方面进行测试: 1.启动时间(可以通过本工具测试): 2.CPU的占用(可以通过本工具测试): 3.内存的占用(可以通过本工具测试): ...

  9. [转载]CMMI之功能点估算法:EI、EQ和EO

    EI.EO.EQ EI是处理来自于应用程序边界外部的一组数据的输入,它的主要目的是维护一个或多个ILF,以及/或者更改系统的行为. EO是输送数据到应用程序边界外部的过程.它的主要目的是通过逻辑处理过 ...

  10. django 事务错误 -- Transaction managed block ended with pending COMMIT/ROLLBACK

    Request Method: GET Request URL: http://192.168.128.111:8000/×××/××××/ Django Version: 1.4.8 Excepti ...