CLR表如何重视“流媒体”功能?

时间:2011-04-13 18:41:50

标签: sql-server f# sqlclr

MSDN Docs on table-valued Sql Clr functions州:

  

Transact-SQL表值函数   具体化调用的结果   函数进入中间表。   ...相比之下,CLR表值   函数表示流式传输   替代。没有要求   整套结果都是   在一张桌子中实现。该   IEnumerable对象返回的   托管函数直接调用   查询的执行计划   调用表值函数,和   结果消耗在一个   增量方式。 ......这也是一个   如果你有更好的替代品   返回了大量行,   因为他们没有必要   在记忆中作为一个整体实现。

Then I find out that no data access is allowed in the 'Fill row' method。这意味着您仍然必须在init方法中执行所有数据访问并将其保留在内存中,等待调用“填充行”。我误解了什么吗? 如果我不将结果强制转换为数组或列表,则会出现错误:'ExecuteReader需要一个开放且可用的连接。连接的当前状态已关闭。'

代码示例:

[<SqlFunction(DataAccess = DataAccessKind.Read, FillRowMethodName = "Example8Row")>]
static member InitExample8() : System.Collections.IEnumerable = 
   let c = cn() // opens a context connection
   // I'd like to avoid forcing enumeration here:
   let data = getData c |> Array.ofSeq
   data :> System.Collections.IEnumerable

static member Example8Row ((obj : Object),(ssn: SqlChars byref)) = 
   do ssn <- new SqlChars(new SqlString(obj :?> string))
   ()

我在这里处理数百万行。有没有办法懒惰地这样做?

3 个答案:

答案 0 :(得分:9)

我假设你正在使用SQL Server 2008.正如微软员工this page所提到的那样,2008年需要使用DataAccessKind.Read标记的方法比2005年更频繁。其中一个时间是TVF参与交易(当我测试时,似乎总是如此)。解决方案是在连接字符串中指定enlist=false,唉,不能与context connection=true组合。这意味着您的连接字符串必须采用典型的客户端格式:Data Source=.;Initial Catalog=MyDb;Integrated Security=sspi;Enlist=false,并且必须至少使用permission_set=external_access创建程序集。以下作品:

using System;
using System.Collections;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

namespace SqlClrTest {
    public static class Test {
        [SqlFunction(
            DataAccess = DataAccessKind.Read,
            SystemDataAccess = SystemDataAccessKind.Read,
            TableDefinition = "RowNumber int",
            FillRowMethodName = "FillRow"
            )]
        public static IEnumerable MyTest(SqlInt32 databaseID) {
            using (var con = new SqlConnection("data source=.;initial catalog=TEST;integrated security=sspi;enlist=false")) {
                con.Open();
                using (var cmd = new SqlCommand("select top (100) RowNumber from SSP1 where DatabaseID = @DatabaseID", con)) {
                    cmd.Parameters.AddWithValue("@DatabaseID", databaseID.IsNull ? (object)DBNull.Value : databaseID.Value);
                    using (var reader = cmd.ExecuteReader()) {
                        while (reader.Read())
                            yield return reader.GetInt32(0);
                    }
                }
            }
        }
        public static void FillRow(object obj, out SqlInt32 rowNumber) {
            rowNumber = (int)obj;
        }
    }
}

F#中的情况与此相同:

namespace SqlClrTest

module Test =

    open System
    open System.Data
    open System.Data.SqlClient
    open System.Data.SqlTypes
    open Microsoft.SqlServer.Server

    [<SqlFunction(
        DataAccess = DataAccessKind.Read,
        SystemDataAccess = SystemDataAccessKind.Read,
        TableDefinition = "RowNumber int",
        FillRowMethodName = "FillRow"
        )>]
    let MyTest (databaseID:SqlInt32) =
        seq {
            use con = new SqlConnection("data source=.;initial catalog=TEST;integrated security=sspi;enlist=false")
            con.Open()
            use cmd = new SqlCommand("select top (100) RowNumber from SSP1 where DatabaseID = @DatabaseID", con)
            cmd.Parameters.AddWithValue("@DatabaseID", if databaseID.IsNull then box DBNull.Value else box databaseID.Value) |> ignore
            use reader = cmd.ExecuteReader()
            while reader.Read() do
                yield reader.GetInt32(0)
        } :> System.Collections.IEnumerable

    let FillRow (obj:obj) (rowNumber:SqlInt32 byref) =
        rowNumber <- SqlInt32(unbox obj)

好消息是:Microsoft considers this a bug

答案 1 :(得分:1)

是的,您需要将结果拉入内存然后从那里返回。虽然意图是你不需要做这样的操作。

您可以在链接到的MSDN文档的其中一个部分中看到该方法的示例(“示例:返回SQL查询的结果”)

虽然实际的电子邮件验证实现使用标量而不是表函数 - 但是为每个输入电子邮件值返回一个bool而不是一个无效的列表。

这些示例有点人为。

你能解释一下你想要实现的目标吗?可能有更好的方法来构建函数。

答案 2 :(得分:1)

您可以做的是使用IEnumerable包装SqlDataReader类,该IEnumerable使用枚举器,当调用其“Next”方法时,在SqlDataReader上执行MoveNext并返回SqlDataReader。然后,您的FillRow方法将SqlDataReader作为一个类。如果你的枚举器关闭数据库连接和SqlDataReader,当它不能再“下一步”时,那么你已经有效地将输出流式传输到FillRows函数。您可以使用ContextConnection = true来执行此操作...

...麻烦在于你必须能够返回实际查询的结果:如果你正在做更复杂的事情来创建你的结果集,那么你就不走运了。