以下是一个假设的场景。
我有非常多的用户名(比如说10,000,000,000,000,000,000,000。是的,我们已经进入了星际时代 :))。每个用户都有自己的数据库。我需要遍历用户列表,并对每个数据库执行一些SQL语句,并打印结果。
由于我学习了函数式编程的好处,并且因为我处理了如此庞大的用户数量,我决定使用F#和纯序列(也称为IEnumerable)来实现这一点。然后我就开始了。
// gets the list of user names
let users() : seq<string> = ...
// maps user name to the SqlConnection
let mapUsersToConnections (users: seq<string>) : seq<SqlConnection> = ...
// executes some sql against the given connection and returns some result
let mapConnectionToResult (conn) : seq<string> = ...
// print the result
let print (result) : unit = ...
// and here is the main program
users()
|> mapUsersToConnections
|> Seq.map mapConnectionToResult
|> Seq.iter print
漂亮?优雅?绝对是。
但是!谁在什么时候处理SqlConnection?
我认为答案mapConnectionToResult
不应该这样做是错误的,因为它对给定的连接的生命周期一无所知。而且,根据mapUsersToConnections
的实现方式和其他各种因素,事情可能会工作或不工作。
由于mapUsersToConnections
是唯一可以访问连接的地方,它必须负责处理SQL连接。
在F#中,可以这样做:
// implementation where we return the same connection for each user
let mapUsersToConnections (users) : seq<SqlConnection> = seq {
use conn = new SqlConnection()
for u in users do
yield conn
}
// implementation where we return new connection for each user
let mapUsersToConnections (users) : seq<SqlConnection> = seq {
for u in users do
use conn = new SqlConnection()
yield conn
}
相当于C#的语法:
// C# -- same connection for all users
IEnumerable<SqlConnection> mapUsersToConnections(IEnumerable<string> users)
{
using (var conn = new SqlConnection())
foreach (var u in users)
{
yield return conn;
}
}
// C# -- new connection for each users
IEnumerable<SqlConnection> mapUsersToConnections(IEnumerable<string> user)
{
foreach (var u in users)
using (var conn = new SqlConnection())
{
yield return conn;
}
}
我所执行的测试表明,对象在正确的时间点得到了正确的处理,即使在并行执行的情况下:对于共享连接,在整个迭代的结尾处执行一次;对于非共享连接,在每个迭代周期后执行。
所以,问题是:我理解得对吗?
编辑:
一些答案友好地指出了代码中的一些错误,我进行了一些更正。以下是完整的可编译示例。
SqlConnection 的使用仅用于示例目的,实际上它可以是任何 IDisposable。
可编译示例
open System
// Stand-in for SqlConnection
type SimpeDisposable() =
member this.getResults() = "Hello"
interface IDisposable with
member this.Dispose() = printfn "Disposing"
// Alias SqlConnection to our dummy
type SqlConnection = SimpeDisposable
// gets the list of user names
let users() : seq<string> = seq {
for i = 0 to 100 do yield i.ToString()
}
// maps user names to the SqlConnections
// this one uses one shared connection for each user
let mapUsersToConnections (users: seq<string>) : seq<SqlConnection> = seq {
use c = new SimpeDisposable()
for u in users do
yield c
}
// maps user names to the SqlConnections
// this one uses new connection per each user
let mapUsersToConnections2 (users: seq<string>) : seq<SqlConnection> = seq {
for u in users do
use c = new SimpeDisposable()
yield c
}
// executes some "sql" against the given connection and returns some result
let mapConnectionToResult (conn:SqlConnection) : string = conn.getResults()
// print the result
let print (result) : unit = printfn "%A" result
// and here is the main program - using shared connection
printfn "Using shared connection"
users()
|> mapUsersToConnections
|> Seq.map mapConnectionToResult
|> Seq.iter print
// and here is the main program - using individual connections
printfn "Using individual connection"
users()
|> mapUsersToConnections2
|> Seq.map mapConnectionToResult
|> Seq.iter print
结果如下:
共享连接: "hello" "hello" ... "正在释放"
个体连接: "hello" "正在释放" "hello" "正在释放"