SQL Server CLR集成 #

一、CLR概述 #

1.1 什么是CLR集成 #

CLR(Common Language Runtime)集成允许在SQL Server中运行.NET代码,扩展T-SQL的功能。

text
CLR集成优势:
├── 使用.NET Framework功能
├── 更好的性能(复杂计算)
├── 类型安全
├── 面向对象编程
└── 代码复用

1.2 CLR应用类型 #

text
CLR应用类型:
├── 存储过程
├── 用户定义函数
├── 触发器
├── 用户定义聚合
├── 用户定义类型
└── 表值函数

二、启用CLR #

2.1 启用CLR集成 #

sql
-- 启用CLR
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE;
GO

-- 验证
SELECT value_in_use FROM sys.configurations WHERE name = 'clr enabled';

2.2 安全设置 #

sql
-- 设置权限集
-- SAFE:只能访问本地数据
-- EXTERNAL_ACCESS:可访问外部资源
-- UNSAFE:无限制

-- 创建非对称密钥
USE master;
GO
CREATE ASYMMETRIC KEY MyKey FROM EXECUTABLE FILE = 'D:\CLR\MyAssembly.dll';

-- 创建登录名
CREATE LOGIN MyLogin FROM ASYMMETRIC KEY MyKey;

-- 授予权限
GRANT EXTERNAL ACCESS ASSEMBLY TO MyLogin;
-- 或
GRANT UNSAFE ASSEMBLY TO MyLogin;

三、创建CLR程序集 #

3.1 C#代码示例 #

csharp
// 简单CLR存储过程
using System;
using System.Data;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;

public class StoredProcedures
{
    [SqlProcedure]
    public static void GetUsers()
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            conn.Open();
            SqlCommand cmd = new SqlCommand("SELECT * FROM users", conn);
            SqlContext.Pipe.ExecuteAndSend(cmd);
        }
    }
    
    [SqlProcedure]
    public static void GetUserById(int userId)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            conn.Open();
            SqlCommand cmd = new SqlCommand(
                "SELECT * FROM users WHERE id = @id", conn);
            cmd.Parameters.AddWithValue("@id", userId);
            SqlContext.Pipe.ExecuteAndSend(cmd);
        }
    }
}

3.2 CLR函数示例 #

csharp
// CLR标量函数
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public class UserDefinedFunctions
{
    [SqlFunction]
    public static SqlString FormatName(SqlString firstName, SqlString lastName)
    {
        if (firstName.IsNull || lastName.IsNull)
            return SqlString.Null;
        
        return new SqlString(firstName.Value + " " + lastName.Value);
    }
    
    [SqlFunction]
    public static SqlDouble CalculateDistance(
        SqlDouble lat1, SqlDouble lon1, 
        SqlDouble lat2, SqlDouble lon2)
    {
        if (lat1.IsNull || lon1.IsNull || lat2.IsNull || lon2.IsNull)
            return SqlDouble.Null;
        
        // Haversine公式计算距离
        double R = 6371; // 地球半径(公里)
        double dLat = ToRadians(lat2.Value - lat1.Value);
        double dLon = ToRadians(lon2.Value - lon1.Value);
        double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
                   Math.Cos(ToRadians(lat1.Value)) * 
                   Math.Cos(ToRadians(lat2.Value)) *
                   Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
        double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
        
        return new SqlDouble(R * c);
    }
    
    private static double ToRadians(double degrees)
    {
        return degrees * Math.PI / 180;
    }
}

3.3 CLR表值函数示例 #

csharp
// CLR表值函数
using System;
using System.Collections;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public class TableValuedFunctions
{
    [SqlFunction(
        FillRowMethodName = "FillRow",
        TableDefinition = "number INT, square INT")]
    public static IEnumerable GenerateNumbers(int max)
    {
        for (int i = 1; i <= max; i++)
        {
            yield return new NumberInfo(i, i * i);
        }
    }
    
    public static void FillRow(object obj, out SqlInt32 number, out SqlInt32 square)
    {
        NumberInfo info = (NumberInfo)obj;
        number = info.Number;
        square = info.Square;
    }
    
    private class NumberInfo
    {
        public int Number { get; set; }
        public int Square { get; set; }
        
        public NumberInfo(int number, int square)
        {
            Number = number;
            Square = square;
        }
    }
}

3.4 CLR聚合示例 #

csharp
// CLR聚合函数
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

[Serializable]
[SqlUserDefinedAggregate(
    Format.Native,
    IsNullIfEmpty = true)]
public struct Concatenate
{
    private SqlString result;
    
    public void Init()
    {
        result = SqlString.Null;
    }
    
    public void Accumulate(SqlString value)
    {
        if (!value.IsNull)
        {
            if (result.IsNull)
                result = value;
            else
                result = result + ", " + value;
        }
    }
    
    public void Merge(Concatenate other)
    {
        Accumulate(other.result);
    }
    
    public SqlString Terminate()
    {
        return result;
    }
}

四、部署CLR程序集 #

4.1 编译程序集 #

bash
# 使用csc编译
csc /target:library /out:MyAssembly.dll MyCode.cs

# 或使用Visual Studio编译

4.2 注册程序集 #

sql
-- 注册程序集(SAFE权限)
CREATE ASSEMBLY MyAssembly
FROM 'D:\CLR\MyAssembly.dll'
WITH PERMISSION_SET = SAFE;

-- 注册程序集(EXTERNAL_ACCESS权限)
CREATE ASSEMBLY MyAssembly
FROM 'D:\CLR\MyAssembly.dll'
WITH PERMISSION_SET = EXTERNAL_ACCESS;

-- 注册程序集(UNSAFE权限)
CREATE ASSEMBLY MyAssembly
FROM 'D:\CLR\MyAssembly.dll'
WITH PERMISSION_SET = UNSAFE;

4.3 创建CLR对象 #

sql
-- 创建CLR存储过程
CREATE PROCEDURE usp_clr_get_users
AS EXTERNAL NAME MyAssembly.StoredProcedures.GetUsers;
GO

-- 创建CLR函数
CREATE FUNCTION dbo.fn_format_name(
    @firstName NVARCHAR(50),
    @lastName NVARCHAR(50)
)
RETURNS NVARCHAR(101)
AS EXTERNAL NAME MyAssembly.UserDefinedFunctions.FormatName;
GO

-- 创建CLR表值函数
CREATE FUNCTION dbo.fn_generate_numbers(@max INT)
RETURNS TABLE (number INT, square INT)
AS EXTERNAL NAME MyAssembly.TableValuedFunctions.GenerateNumbers;
GO

-- 创建CLR聚合
CREATE AGGREGATE dbo.Concatenate(@value NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
EXTERNAL NAME MyAssembly.Concatenate;
GO

五、使用CLR对象 #

5.1 使用CLR存储过程 #

sql
-- 执行CLR存储过程
EXEC usp_clr_get_users;

-- 带参数
EXEC usp_clr_get_user_by_id @userId = 1;

5.2 使用CLR函数 #

sql
-- 使用CLR标量函数
SELECT 
    name,
    dbo.fn_format_name(first_name, last_name) AS full_name
FROM users;

-- 使用CLR表值函数
SELECT * FROM dbo.fn_generate_numbers(10);

-- 使用CLR聚合
SELECT 
    department_id,
    dbo.Concatenate(name) AS all_names
FROM users
GROUP BY department_id;

六、管理CLR程序集 #

6.1 查看程序集 #

sql
-- 查看所有程序集
SELECT 
    name,
    permission_set_desc,
    create_date,
    modify_date
FROM sys.assemblies;

-- 查看程序集文件
SELECT 
    a.name AS assembly_name,
    f.name AS file_name
FROM sys.assemblies a
INNER JOIN sys.assembly_files f ON a.assembly_id = f.assembly_id;

6.2 修改程序集 #

sql
-- 更新程序集
ALTER ASSEMBLY MyAssembly
FROM 'D:\CLR\MyAssembly_v2.dll';

-- 添加依赖程序集
ALTER ASSEMBLY MyAssembly
ADD FILE FROM 'D:\CLR\Helper.dll';

6.3 删除CLR对象 #

sql
-- 删除CLR函数
DROP FUNCTION dbo.fn_format_name;

-- 删除CLR存储过程
DROP PROCEDURE usp_clr_get_users;

-- 删除CLR聚合
DROP AGGREGATE dbo.Concatenate;

-- 删除程序集
DROP ASSEMBLY MyAssembly;

七、CLR最佳实践 #

7.1 何时使用CLR #

text
适合使用CLR的场景:
├── 复杂计算
├── 字符串处理
├── 正则表达式
├── 外部资源访问
├── 自定义聚合
└── 性能敏感操作

不适合使用CLR的场景:
├── 简单数据操作
├── 标准SQL可完成
├── 数据量大
└── 频繁调用

7.2 性能考虑 #

sql
-- CLR vs T-SQL性能对比
-- CLR适合:CPU密集型操作
-- T-SQL适合:数据访问操作

-- 使用CLR进行复杂计算
CREATE FUNCTION dbo.fn_complex_calculation(@param FLOAT)
RETURNS FLOAT
AS EXTERNAL NAME MyAssembly.Calculations.ComplexCalculation;

-- 使用T-SQL进行数据访问
SELECT * FROM users WHERE id = 1;

7.3 安全考虑 #

text
CLR安全建议:
├── 优先使用SAFE权限集
├── 避免使用UNSAFE
├── 验证输入参数
├── 处理异常
├── 限制程序集权限
└── 定期审计CLR代码

八、调试CLR #

8.1 调试方法 #

csharp
// 使用SqlContext.Pipe发送调试信息
[SqlProcedure]
public static void DebugExample()
{
    SqlContext.Pipe.Send("Debug message");
    SqlContext.Pipe.Send("Variable value: " + someValue);
}

8.2 错误处理 #

csharp
// CLR错误处理
[SqlFunction]
public static SqlString SafeOperation(SqlString input)
{
    try
    {
        // 操作
        return ProcessInput(input);
    }
    catch (Exception ex)
    {
        // 记录错误
        SqlContext.Pipe.Send("Error: " + ex.Message);
        return SqlString.Null;
    }
}

九、总结 #

CLR集成要点:

方面 说明
权限集 SAFE、EXTERNAL_ACCESS、UNSAFE
存储过程 [SqlProcedure]
函数 [SqlFunction]
聚合 [SqlUserDefinedAggregate]
表值函数 IEnumerable + FillRow

最佳实践:

  1. 优先使用SAFE权限集
  2. 用于复杂计算
  3. 正确处理NULL值
  4. 添加错误处理
  5. 性能测试

恭喜你完成了SQL Server完全指南的学习!

最后更新:2026-03-27