如何在并行查询计划中允许 SQL CLR 函数 运行 并且还具有数据访问权限

How to allow a SQL CLR function to run in parallel query plan and also have data access permissions

我编写了一些 SQL CLR 函数 (UDF),它们从托管在 IBM iSeries 上的外部 DB2 数据库中读取数据(使用 IBM DB2 .Net Provider)。为了使该函数具有读取此数据的必要权限,我需要使用 SqlFunction 属性修饰该函数,并将 DataAccess 属性 设置为 DataAccessKind.Read。我还将程序集部署为 UNSAFE。

从DB2数据库读取数据的时间相对较慢(例如,最简单的ExecuteScalar需要3ms)。

我使用这些 UDF 有效地将 DB2 数据库中的数据合并到 Sql 服务器视图中。

例如,假设我的 UDF 定义为

[SqlFunction(DataAccess = DataAccessKind.Read, IsDeterministic = true)] 
public static SqlMoney GetCostPrice(SqlString partNumber)
{
    decimal costPrice;
    // open DB2 connection and retrieve cost price for part
    return new SqlMoney(costPrice);
}

然后在我的 SQL 视图中使用它:

select Parts.PartNumber,
       dbo.GetCostPrice(Parts.PartNumber) as CostPrice
from Parts

如果我可以 运行 我的 SQL 具有并行查询计划的视图,性能问题可能会受到重大影响。

有关于如何强制执行查询计划 运行 并行而不是串行的技术,但这些技术受到 SQL 服务器的限制,其中之一是 SQL CLR 定义的函数必须有 DataAccess = DataAccessKind.None.

但是,如果我将 DataAcessKind 设置为 None,那么当我尝试在该函数中打开任何 DbConnection 时会出现异常。

这就是我的问题!如何在并行查询计划中 运行 我的 UDF,同时仍然允许它从外部数据库读取数据?

我必须解决这个问题的最佳方法是在我的 SqlFunction 属性中硬编码 DataAccess = DataAccessKind.None,然后在 运行 时间,在函数使用代码访问安全提升权限,以便后续代码有权打开 DbConnection 对象。

但我不知道该怎么做?作为实验,我尝试了以下

    [SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)]
    public static SqlMoney TestFunction()
    {
        var sqlPerm = new SqlClientPermission(PermissionState.Unrestricted);
        sqlPerm.Assert();

        using (var conn = new SqlConnection("context connection=true"))
        {
            conn.Open();
        }

        return new SqlMoney();
    }

并从 Sql Server Management Studio 调用:

select dbo.TestFunction()

但我继续收到安全异常...

执行用户定义的例程或聚合时出现 .NET Framework 错误 "TestFunction": System.InvalidOperationException: 在此上下文中不允许数据访问。上下文是未标记 DataAccessKind.Read 或 SystemDataAccessKind.Read 的函数或方法,是从 Table 值函数的 FillRow 方法获取数据的回调,或者是 UDT 验证方法。 System.InvalidOperationException: 在 System.Data.SqlServer.Internal.ClrLevelContext.CheckSqlAccessReturnCode(SqlAccessApiReturnCode eRc) 在 System.Data.SqlServer.Internal.ClrLevelContext.GetCurrentContext(SmiEventSink 接收器,布尔值 throwIfNotASqlClrThread,布尔值 fAllowImpersonation) 在 Microsoft.SqlServer.Server.InProcLink.GetCurrentContext(SmiEventSink eventSink) 在 Microsoft.SqlServer.Server.SmiContextFactory.GetCurrentContext() 在 System.Data.SqlClient.SqlConnectionFactory.GetContextConnection(SqlConnectionString 选项,对象 providerInfo,DbConnection owningConnection) 在 System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions 选项、对象 poolGroupProviderInfo、DbConnectionPool 池、DbConnection owningConnection) 在 System.Data.ProviderBase.DbConnectionFactory.CreateNonPooledConnection(DbConnection owningConnection,DbConnectionPoolGroup poolGroup) 在 System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection) 在 System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection,DbConnectionFactory connectionFactory) 在 System.Data.SqlClient.SqlConnection.Open() 在 UserDefinedFunctions.UserDefinedFunctions.TestFunction()

有人有什么想法吗?

提前致谢。

(顺便说一句,我 运行 宁 SQL 2008 使用 .Net 3.5)

据我的测试(针对 SqlConnection 到 SQL 服务器)显示,这只能通过使用常规/外部连接(即 而不是 Context Connection = true) and 在Connection String中加入Enlist关键字,设置为false:

Server=DB2; Enlist=false;

但是在使用 Context Connection = true 时似乎没有任何方法可以使这项工作正常进行。上下文连接自动成为当前事务的一部分,使用上下文连接时不能指定任何其他连接字符串关键字。交易与它有什么关系?好吧,Enlist 的默认值是 true,所以即使您确实有常规/外部连接,如果您不指定 Enlist=false;,那么您得到的也是相同的

Data access is not allowed in this context.

您现在遇到的错误。

当然,这是一个有争议的问题,因为在这种特殊情况下使用上下文连接没有任何意义,因为它需要使用链接服务器,并且在对该问题的评论中指出"The Linked Server Db2 Providers (from MS) are unbelievably slow".

也有人指出,使用带有 Suppress 选项的 TransactionScope 可能会奏效。这是行不通的,因为如果两个 DataAccessSystemDataAccess 设置为 None(这是它们的默认值)。

此外,关于

elevate the UserDataAccess status of a UDF at runtime.

这是不可能的,因为 UserDataAccess 不是 运行 时间选项。确定何时执行 CREATE FUNCTION 语句(定义为 AS EXTERNAL NAME [Assembly]... 的语句。UserDataAccessSystemDataAccess 属性是与函数一起存储的元数据. 您可以使用 OBJECTPROPERTYEX 内置函数查看其中任何一个的设置:

SELECT OBJECTPROPERTYEX(OBJECT_ID(N'SchemaName.FunctionName'), 'UserDataAccess');

您的两个选择似乎是:

  1. 使用支持 Enlist 关键字的提供程序,以便可以将其设置为 false,或者如果它默认不登记,则不需要 DataAccess 设置为 Read。根据要查看的建议文档 ( Integrating DB2 Universal Universal Database for iSeries with for iSeries with Microsoft ADO .NET ),选项似乎是:

    • OleDb
    • ODBC
    • 用于 LUW .NET 的 IBM DB2
  2. 建立一个 middle-tear 作为一个 web 服务,SQLCLR 函数可以将请求传递给它,它会使用任何提供者来获取信息,它会响应信息。然后 SQLCLR 函数不进行任何直接数据访问, Web 服务可以进行自己的缓存(您说过源数据不会经常更改)以提高性能(即使只缓存值 1 - 5 分钟)。是的,这确实引入了外部依赖性,但它应该可以正常工作。