错误:读取器关闭时无效尝试调用Read?

时间:2018-05-31 01:26:27

标签: f# ado.net

定义了以下类型。

type DownloadedItem = { Period: DateTime; Name: string } with
    static member fromRdr(rdr:IDataReader) = 
        { Period = rdr.GetDateTime 0; Name = rdr.GetString 1 }
    static member asSeq (rdr:IDataReader) = seq { 
        while rdr.Read() do yield DownloadedItem.fromRdr rdr } 

然后尝试从数据库表中获取数据。

let files =
    let sql = "exec [sp_name] @StartPeriod"
    use conn = new SqlConnection(Shared.connectionString)
    use cmd = new SqlCommand(sql, conn)
    cmd.Parameters.Add("@StartPeriod", SqlDbType.Date).Value <- StartPeriod
    conn.Open()
    use reader = cmd.ExecuteReader()
    reader
    |> DownloadedItem.asSeq

上面的表达式可以毫无问题地发送到F#交互式窗口。

但是,评估files;;出现以下错误?

val it : seq<DownloadedItem> =
  Error: Invalid attempt to call Read when reader is closed.

1 个答案:

答案 0 :(得分:2)

序列很懒惰。这意味着在某人试图获取其元素之前,不会对序列进行评估。

试试这个:

let s = seq {
   for i in 1..1000 do
       printfn "%d" i
       yield i
}

> Seq.take 3 s

此程序仅打印1到3的数字,即使序列的定义为1000.这是因为Seq.take 3调用仅枚举序列的前三个元素,并且评估不会进一步进行

现在让我们再迈出一步:

let s = 
    printfn "Creating sequence"
    let result = seq { printfn "Returning item"; yield 42 }
    printfn "Done creating sequence"
    result

执行此代码打印&#34;创建序列&#34;,然后&#34;完成创建序列&#34;。但它没有打印&#34;返回项目&#34;一点都不为什么不?我们构造了序列,但从未评估它。现在,如果我执行s,那么&#34;返回项目&#34;将被打印。

看看发生了什么? s的主体在结束序列之前完成了的执行。

在您的代码中发生同样的事情:files的主体在评估结果序列之前完成执行。当files的主体完成执行时,reader会被处置,因为它与use绑定。因此,当您评估序列时,reader不再有效,因此您会收到错误。

要解决此问题,您需要确保在评估序列的整个过程中reader保持有效。唯一可行的方法是在序列正文中包含所有use

let files = seq {
    let sql = "exec [sp_name] @StartPeriod"
    use conn = new SqlConnection(Shared.connectionString)
    use cmd = new SqlCommand(sql, conn)
    cmd.Parameters.Add("@StartPeriod", SqlDbType.Date).Value <- StartPeriod
    conn.Open()
    use reader = cmd.ExecuteReader()
    while rdr.Read() do yield DownloadedItem.fromRdr reader
}

这样,每当有人试图枚举序列时,整个初始化就会发生,并且reader保持有效,直到枚举完成。