Как табличная функция CLR является «потоковой»? - PullRequest
10 голосов
/ 13 апреля 2011

Документы MSDN для табличных функций Sql Clr состояния:

Табличные функции Transact-SQL материализуют результаты вызова функции в промежуточную таблицу.... Напротив, табличные функции CLR представляют собой потоковую альтернативу.Не требуется, чтобы весь набор результатов был реализован в одной таблице.Объект IEnumerable, возвращаемый управляемой функцией, напрямую вызывается планом выполнения запроса, который вызывает табличную функцию, а результаты используются в пошаговом режиме.... Это также лучшая альтернатива, если вы возвращаете очень большое количество строк, потому что они не должны быть материализованы в памяти в целом.

Тогда я узнаю, чтодоступ к данным не разрешен в методе «Fill row» .Это означает, что вам все равно придется делать все ваши данные в методе init и хранить их в памяти, ожидая вызова «Fill row».Я что-то не так понял?Если я не форсирую свои результаты в массив или список, я получаю сообщение об ошибке: «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 ]

9 голосов
/ 06 мая 2011

Я предполагаю, что вы используете SQL Server 2008. Как упомянул сотрудник Microsoft на на этой странице , 2008 требует, чтобы методы отмечались с помощью DataAccessKind. Читать намного чаще, чем в 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 считает это ошибкой .

1 голос
/ 23 января 2013

Что вы можете сделать, это обернуть класс SqlDataReader в IEnumerable, который использует перечислитель, который при вызове метода «Next» выполняет MoveNext в SqlDataReader и возвращает SqlDataReader. Затем ваш метод FillRow ожидает SqlDataReader как класс. Если ваш перечислитель закрывает соединение с базой данных и SqlDataReader, когда он больше не может «следующий», тогда вы эффективно перенаправили свой вывод в функцию FillRows. Вы также можете сделать это с ContextConnection = true ...

... проблема в том, что вы должны иметь возможность возвращать результаты реального запроса: если вы делаете более сложные вещи для создания набора результатов, то вам не повезло.

1 голос
/ 27 апреля 2011

Да, вам нужно вытянуть результаты в память, а затем вернуться оттуда. Хотя предполагается, что вы избежите необходимости выполнять такие операции.

Пример такого подхода можно увидеть в одном из разделов документа MSDN, с которым вы связаны («Пример: возврат результатов запроса SQL»)

Примеры немного надуманные, хотя в реальной реализации проверки электронной почты будет использоваться скалярная, а не табличная функция - возвращать логическое значение для каждого входного значения электронной почты, а не список недействительных.

Можете ли вы объяснить немного больше о том, чего вы пытаетесь достичь? Возможно, есть лучший способ структурирования функции.

...