定义了以下类型。
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.
答案 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
保持有效,直到枚举完成。