【问题标题】:Error: Invalid attempt to call Read when reader is closed?错误:阅读器关闭时调用 Read 的尝试无效?
【发布时间】:2018-05-31 01:26:27
【问题描述】:

定义了以下类型。

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.

【问题讨论】:

  • 如果将files 设为函数会怎样? let files() = ... 现在它是某个临时模块中的静态绑定,初始化可能会很棘手……而且您在其中有 use 绑定。

标签: f# ado.net


【解决方案1】:

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

试试这个:

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 在评估序列的整个过程中保持有效。唯一可行的方法是在序列主体中包含所有uses:

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 一直有效,直到枚举完成。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多