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

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

从DB2数据库读取数据所花费的时间相对较慢(例如,对于最简单的ExecuteScalar,为3ms).

我使用这些UDF将DB2数据库中的数据有效地合并到Sql Server视图中.

例如,假设我的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 Server强加的限制,其中之一是SQL CLR定义的函数必须具有DataAccess = DataAccessKind.None.

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

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

我必须解决这个问题的最好方法是在我的SqlFunction属性中硬编码DataAccess = DataAccessKind.None,然后在运行时,在函数体内使用Code Access Security提升权限,以便后续代码有权打开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()

但我继续得到一个安全例外……

在执行用户定义的例程或聚合“TestFunction”期间发生.NET Framework错误:
System.InvalidOperationException:在此上下文中不允许数据访问.上下文是未使用DataAccessKind.Read或SystemDataAccessKind.Read标记的函数或方法,是从表值函数的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选项,Object providerInfo,DbConnection owningConnection)
   在System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions选项,Object 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()
   at UserDefinedFunctions.UserDefinedFunctions.TestFunction()

有人有任何想法吗?

提前致谢.

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

最佳答案 至于我的测试(针对SqlConnection到SQL Server)显示,这只能通过使用常规/外部连接(即不是Context Connection = true)并将Enlist关键字添加到Connection String,设置为false来实现:

Server=DB2; Enlist=false;

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

Data access is not allowed in this context.

你现在得到的错误.

当然,这是一个没有实际意义的问题,因为在这种特殊情况下使用Context Connection没有任何意义,因为它需要使用链接服务器,并且在问题的评论中指出“链接服务器Db2”提供商(来自MS)的速度令人难以置信“.

还有人指出,使用带有Suppress选项的TransactionScope可能会有效.这不起作用,因为如果DataAccess和SystemDataAccess都设置为None(这是它们的默认值),则不允许实例化TransactionScope对象(具有三个选项中的任何一个:Required,RequiresNew或Suppress).

另外,关于欲望

elevate the UserDataAccess status of a UDF at runtime.

由于UserDataAccess不是运行时选项,因此这是不可能的.确定执行CREATE FUNCTION语句的时间(具有AS EXTERNAL NAME [Assembly] …的语句作为定义.UserDataAccess和SystemDataAccess属性是与Function一起存储的元数据.您可以看到设置通过使用OBJECTPROPERTYEX内置函数中的任何一个:

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

你的两个选择似乎是:

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

> OleDb
> ODBC
> IBM DB2 for LUW .NET

>构建一个中间撕裂是一个SQLCLR功能可以传递请求的Web服务,它将使用任何提供者来获取信息,它将响应信息.然后,SQLCLR函数不进行任何直接数据访问,并且Web服务可以执行自己的缓存(您说源数据不会经常更改)以提高性能(即使只缓存值1到5分钟) ).是的,这确实引入了外部依赖,但它应该按照需要工作.

点赞