很早就知道可以用 .NET 为 SQL Server2005 及以上版本编写存储过程、触发器和存储过程的,不过之前开发的系统要么因为历史原因用的是 SQL2000 要么根本用不着在 SQL Server 中启用 CLR ,所以一直没有尝试。最近因为项目的原因,在这方面做了一个调研,现在在这里分享一下心得。
首先要说明的是要在 SQL Server 中启用 CLR 必须是在 SQL Server2005 及以上版本,其次在默认情况下是没有启用 CLR 的,必须要显示设置为启用。比如我们要在 ArticleCollectorDB 数据库中运行用 .NET 编写的函数或者存储过程,至少先要进行下面的 SQL 语句:
exec sp_configure 'clr enabled', 1;--在SQL Server中启用CLR reconfigure; go --在ArticleCollectorDB数据库中设置TRUSTWORTHY为ON ALTER DATABASE [ArticleCollectorDB] SET TRUSTWORTHY ON
这时可能会得到提示要重新启动 SQL Server ,如果有此提示则重新启动一下。
接着我们在 VS 中进行编码,在这里我们将分别编写一个名为 IsMatch 的函数和一个名为 SendMail 存储过程。在 VS 中创建一个名为 NetSkycn.Data 的类库项目,添加一个 SqlCLR 的类,代码如下:
using System.Data.SqlTypes; using System.Net; using System.Net.Mail; using System.Security.Permissions; using System.Text.RegularExpressions; using Microsoft.SqlServer.Server; namespace NetSkycn.Data { /// <summary> /// 在SQL Server环境中执行的CLR方法,注意提供给SQL Server调用的方法必须有SqlFunction/SqlProcedure Attribute /// 作者:周公 /// 创建日期:2012-05-09 /// 博客地址:http://blog.csdn.net/zhoufoxcn http://zhoufoxcn.blog.51cto.com /// 新浪微博地址:http://weibo.com/zhoufoxcn /// </summary> public sealed class SqlCLR { /// <summary> /// 判断字符串是否匹配正则表达式 /// </summary> /// <param name="source">要匹配的文本</param> /// <param name="pattern">进行匹配的正则表达式</param> /// <param name="options">正则表达式匹配选项,1为忽略大小写,2为多行匹配,3为忽略大小写且多行匹配</param> /// <returns></returns> [SqlFunction(IsDeterministic = true, DataAccess = DataAccessKind.None)] public static SqlBoolean IsMatch(string source, string pattern,int options) { if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(pattern)) { return SqlBoolean.False; } RegexOptions regexOptions=RegexOptions.None; int optionIgnoreCase = 1; int optionMultiline = 2; if ((options & optionIgnoreCase) != 0) { regexOptions = regexOptions | RegexOptions.IgnoreCase; } if ((options & optionMultiline) != 0) { regexOptions = regexOptions | RegexOptions.Multiline; } return (SqlBoolean)(Regex.IsMatch(source, pattern, regexOptions)); } /// <summary> /// 发送邮件 /// </summary> /// <param name="to">收件人邮件地址</param> /// <param name="from">发件人邮件地址</param> /// <param name="subject">邮件主题</param> /// <param name="body">邮件内容</param> /// <param name="username">登录smtp主机时用到的用户名,注意是邮件地址'@'以前的部分</param> /// <param name="password">登录smtp主机时用到的用户密码</param> /// <param name="smtpHost">发送邮件用到的smtp主机</param> [SqlProcedure] [SmtpPermission(SecurityAction.Assert)] [SecurityPermission(SecurityAction.Assert)] public static void SendMail(string to, string from, string subject, string body, string userName, string password, string smtpHost) { MailAddress addressFrom = new MailAddress(from); MailAddress addressTo = new MailAddress(to); MailMessage message = new MailMessage(addressFrom, addressTo); message.Subject = subject;//设置邮件主题 message.IsBodyHtml = true;//设置邮件正文为html格式 message.Body = body;//设置邮件内容 SmtpClient client = new SmtpClient(smtpHost); //设置发送邮件身份验证方式 //注意如果发件人地址是abc@def.com,则用户名是abc而不是abc@def.com client.Credentials = new NetworkCredential(userName, password); client.Send(message); } } }
编译通过之后,记住类库的物理全路径,比如: F:\VS2008\netskycn\NetSkycn.Data\bin\Release\NetSkycn.Data.dll ,在这里要强调几点:一、对于将来提供给 SQL Server 调用的函数或者存储过程必须是静态方法,并且还必须带有 SqlFunction 或者 SqlProcedure 属性;二、对于一些需要访问外部网络资源和安全属性的还必须添加响应的属性(如本例中的 SendMail 方法,如果没有添加响应的属性在创建 SQL Function/Procedure 时会出现错误提示)。
现在我们开始遵循先为 SQL Server 创建程序集、后创建函数或者存储过程的顺序来操作,在操作过程中用到的 SQL 语句如下:
--在ArticleCollectorDB数据库中设置TRUSTWORTHY为ON ALTER DATABASE [ArticleCollectorDB] SET TRUSTWORTHY ON --如果已经存在该对象则删除 IF EXISTS(SELECT * FROM SYS.SYSOBJECTS WHERE NAME='SendMail' AND XTYPE='PC') DROP PROCEDURE SendMail --如果已经存在该对象则删除 IF EXISTS(SELECT * FROM SYS.SYSOBJECTS WHERE NAME='IsMatch' AND XTYPE='FS') DROP FUNCTION IsMatch --如果已经存在SqlCLR程序集则删除该程序集 IF EXISTS(SELECT * FROM SYS.ASSEMBLIES WHERE NAME='SqlCLR') DROP ASSEMBLY SqlCLR --在SQL Server中创建程序集,,创建的程序集名为SqlCLR CREATE ASSEMBLY SqlCLR FROM 'F:\VS2008\netskycn\NetSkycn.Data\bin\Release\NetSkycn.Data.dll' WITH PERMISSION_SET = UNSAFE GO --从CLR程序集中创建函数,函数名为IsMatch,有三个参数, --[SqlCLR]是SQL Server中程序集名 --[NetSkycn.Data.SqlCLR]是.NET中的类的全名(命名空间及类名) --[IsMatch]是.NET中类的函数名 CREATE FUNCTION [dbo].[IsMatch] ( @source AS NVARCHAR(200), @pattern AS NVARCHAR(200), @option INT=3 ) RETURNS BIT AS EXTERNAL NAME [SqlCLR].[NetSkycn.Data.SqlCLR].[IsMatch]; GO --从CLR程序集中创建函数,函数名为IsMatch,有三个参数, --[SqlCLR]是SQL Server中程序集名 --[NetSkycn.Data.SqlCLR]是.NET中的类的全名(命名空间及类名) --[SendMail]是.NET中类的函数名 CREATE PROCEDURE [dbo].[SendMail] ( @to AS NVARCHAR(200), @from AS NVARCHAR(200), @subject AS NVARCHAR(200), @body AS NVARCHAR(MAX), @userName AS NVARCHAR(200), @password AS NVARCHAR(200), @smtpHost AS NVARCHAR(200) ) AS EXTERNAL NAME [SqlCLR].[NetSkycn.Data.SqlCLR].[SendMail]; GO
如果没有得到任何错误提示,则表示创建函数和存储过程成功。至此我们会看到如下情形:
这表示创建成功。
测试创建函数的 SQL 语句(查找 article 表中 title 字段是 3 至 5 个字段的数据):
select * from article where dbo.IsMatch(Title,'^[\u4e00-\u9fa5]{3,5}$',3)=1
测试创建存储过程的 SQL 语句:
exec [dbo].SendMail @to='test@qq.com',@from='webmaster@qq.com',@subject='test',@body='This mail was sent by SQL Procedure',@userName='webmaster',@password='123',@smtpHost='smtp.qq.com'
以上代码在 SQL Server 2005 中文企业版、 SQL Server 2008 英文企业版测试通过。
可以看出在一些SQL语句不够灵活的情况下,可以使用.NET来编写存储过程和函数,通过以上步骤之后和调用SQL语句写的存储过程和函数没有区别,极大地方便了编程。
周公
2012-05-12