定义了以下类型。
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.
最佳答案
序列是惰性的。这意味着在有人尝试获取序列的元素之前,不会对序列进行求值。
试试这个:
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
执行此代码会打印“创建序列”,然后打印“完成创建序列”。但它根本不打印“返回”。为什么不?我们构建了序列,但从未评估它。现在,如果我执行 s
,则会打印“Returning item”。
看看发生了什么? s
的主体在计算结果序列之前完成执行。
您的代码中也会发生同样的情况:files
的主体在计算结果序列之前完成执行。当files
的主体执行完毕后,reader
就会被释放,因为它与use
绑定(bind)在一起。因此,当您开始评估序列时,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
保持有效,直到枚举完成。
关于f# - 错误: Invalid attempt to call Read when reader is closed?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50615393/