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))
()
我在这里处理数百万行。有没有办法懒惰地这样做?
答案 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)
答案 1 :(得分:1)
是的,您需要将结果拉入内存然后从那里返回。虽然意图是你不需要做这样的操作。
您可以在链接到的MSDN文档的其中一个部分中看到该方法的示例(“示例:返回SQL查询的结果”)
虽然实际的电子邮件验证实现使用标量而不是表函数 - 但是为每个输入电子邮件值返回一个bool而不是一个无效的列表。
这些示例有点人为。你能解释一下你想要实现的目标吗?可能有更好的方法来构建函数。
答案 2 :(得分:1)
您可以做的是使用IEnumerable包装SqlDataReader类,该IEnumerable使用枚举器,当调用其“Next”方法时,在SqlDataReader上执行MoveNext并返回SqlDataReader。然后,您的FillRow方法将SqlDataReader作为一个类。如果你的枚举器关闭数据库连接和SqlDataReader,当它不能再“下一步”时,那么你已经有效地将输出流式传输到FillRows函数。您可以使用ContextConnection = true来执行此操作...
...麻烦在于你必须能够返回实际查询的结果:如果你正在做更复杂的事情来创建你的结果集,那么你就不走运了。