首页 > 解决方案 > 错误:阅读器关闭时调用 Read 的尝试无效?

问题描述

定义了以下类型。

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.

标签: f#ado.net

解决方案


序列是惰性的。这意味着在有人尝试获取其元素之前,不会对序列进行评估。

尝试这个:

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

执行此代码将打印“创建序列”,然后打印“完成创建序列”。但它根本不打印“Returning item”。为什么不?我们已经构建了序列,但从未评估过它。现在,如果我执行s,则会打印“Returning item”。

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

同样的事情发生在您的代码中:在files评估结果序列之前完成执行的主体。并且当主体files完成执行时,将reader被处置,因为它与use. 因此,当你开始评估序列时,reader它不再有效,所以你得到了错误。


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

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在枚举完成之前保持有效。


推荐阅读