首页 > 代码库 > ADO.NET

ADO.NET

ADO.NET

 

关系型数据库管理系统(Relational database management systems,RDBMSs)是数据存储最普遍的形式。有了 ADO.NET,System.Data 和相关的命名空间,访问关系型数据更容易。这一节,我们将学习多种方法在F# 中使用 ADO.NET。

 

注意

所有的数据库提供程序都使用连接字符串指定数据库连接。在http://www.connectionstrings.com 上有关于连接字符串的汇总。

 

这一节的所有例子都使用 SQL Server 2005 Express Edition 和 AdventureWorks 数据库,两个都可以从http://www.microsoft.com 自由下载;把这些例子导出到其他关系型数据库也是很容易的。要在 SQL Server 2005 Express Edition 中使用这个数据库,需要下面的连接设置,或者根据你的系统作相应调整:

 

<connectionStrings>

 <add

  name="MyConnection"

  connectionString="

Database=AdventureWorks;

Server=.\SQLExpress;

Integrated Security=SSPI;

AttachDbFilename=

    C:\ProgramFiles\Microsoft SQL Server\MSSQL.1\MSSQL\Data\AdventureWorks_Data.mdf"

  providerName="System.Data.SqlClient" />

</connectionStrings>

 

[

上面的配置文件缺少根结点:<configuration></configuration>

用下面的连接字符串也可以:

 

<configuration>

  <connectionStrings>

  <add

    name="MyConnection"

    connectionString="

    DataSource=.\sqlexpress;

    InitialCatalog=AdventureWorks;

    IntegratedSecurity=True;

    Pooling=False"

    providerName=".NET Framework Data Providerfor SQL Server" />

  </connectionStrings>

</configuration>

]

 

我们将在“ADO.NET 扩展”一节讨论访问其他关系型数据库的选项。下面的例子演示了访问数据库的一种简单方法:

 

open System.Configuration

open System.Data

open System.Data.SqlClient

 

// get the connection string

let connectionString =

  letconnectionSetting =

   ConfigurationManager.ConnectionStrings.["MyConnection"]

 connectionSetting.ConnectionString

 

let main() =

  //create a connection

  useconnection = new SqlConnection(connectionString)

 

  //create a command

  letcommand =

    connection.CreateCommand(CommandText= "select * from Person.Contact",

                            CommandType =CommandType.Text)

 

  //open the connection

 connection.Open()

 

  //open a reader to read data from the DB

  usereader = command.ExecuteReader()

 

  //fetch the ordinals

  lettitle = reader.GetOrdinal("Title")

  letfirstName = reader.GetOrdinal("FirstName")

  letlastName = reader.GetOrdinal("LastName")

 

  //function to read strings from the data reader

  letgetString (r: #IDataReader) x =

   if r.IsDBNull(x) then ""

   else r.GetString(x)

 

  //read all the items

 while reader.Read() do

   printfn "%s %s %s"

     (getString reader title )

     (getString reader firstName)

     (getString reader lastName)

 

main()

 

前面代码的运行结果如下:

 

Mr. Gustavo Achong

Ms. Catherine Abel

Ms. Kim Abercrombie

Sr. Humberto Acevedo

Sra. Pilar Ackerman

Ms. Frances Adams

Ms. Margaret Smith

...

 

在前面的例子中,首先看到的是将要用到的连接字符串;之后,就创建连接:

 

using (new SqlConnection(connectionString))

 

注意,我们使用 use 关键字代替 let,这是保证在完成所做工作之后,能够关闭连接;use 关键字能够保证当连接超出其作用域后,会调用Dispose 方法。然后,使用连接创建 SqlCommand 类,再设置它的 CommandText 属性,指定需要执行的命令:

 

let command =

 connection.CreateCommand(CommandText = "select * fromPerson.Contact",

                          CommandType =CommandType.Text)

 

接下来,执行这个命令创建 SqlDataReader 类,用这个类读数据库:

 

use reader = command.ExecuteReader()

 

这个命令也使用 use 绑定,代替 let,确保可以正确关闭。

如果必须为每个查询写大量的代码,那么,在 F# 中可能并不需要写数据访问的代码。有一个简单的方法,创建执行命令的库函数,这样。就能把使用的连接、运行的命令参数化。

下面的例子演示如何写出这样一个函数,实现 execCommand 函数,使用 Seq.generate,它能产生可枚举的序列集合。generate 函数有三个参数:第一个函数打开到数据库的连接,在每次枚举结果集合时调用,这个函数被称为开启者(opener),也能够用它来打开对文件的连接;第二个函数生成集合中的元素,称为生产者(generator)。这段代码用一行数据创建了字典(Dictionary)对象;第三个函数是用于关闭数据库或读取文件的连接:

open System.Configuration

open System.Collections.Generic

open System.Data

open System.Data.SqlClient

open System.Data.Common

open System

 

/// create and open an SqlConnection objectusing the connection string found

/// in the configuration file for the givenconnection name

let openSQLConnection(connName:string) =

  letconnSetting = ConfigurationManager.ConnectionStrings.[connName]

  letconn = new SqlConnection(connSetting.ConnectionString)

  conn.Open()

  conn

 

/// create and execute a read command for aconnection using

/// the connection string found in theconfiguration file

/// for the given connection name

let openConnectionReader connName cmdString=

  letconn = openSQLConnection(connName)

  letcmd = conn.CreateCommand(CommandText=cmdString,

                             CommandType =CommandType.Text)

  letreader = cmd.ExecuteReader(CommandBehavior.CloseConnection)

  reader

 

/// read a row from the data reader

let readOneRow (reader: #DbDataReader) =

  ifreader.Read() then

    letdict = new Dictionary<string, obj>()

    forx in [ 0 .. (reader.FieldCount - 1) ] do

     dict.Add(reader.GetName(x), reader.[x])

    Some(dict)

  else

    None

 

/// execute a command using theSeq.generate

let execCommand (connName: string)(cmdString: string) =

  Seq.generate

    //This function gets called to open a connection and create a reader

     (fun () -> openConnectionReader connNamecmdString)

    //This function gets called to read a single item in

    //the enumerable for a reader/connection pair

     (fun reader -> readOneRow(reader))

     (fun reader -> reader.Dispose())

 

[

以下的两段出现在这里,重复。

已经在后面单独出现了。

]

 

/// open the contacts table

let contactsTable =

  execCommand

    "MyConnection"

    "select* from Person.Contact"

 

/// print out the data retrieved from thedatabase

for row in contactsTable do

  forcol in row.Keys do

    printfn"%s = %O" col (row.Item(col))

 

[

除 System.Configuration、System.Data 以外,还需要 Fsharp.PowerPack

]

 

定义了execCommand 函数之后,再访问数据库就变得非常容易了。调用 execCommand,把需要的连接和命令作为参数传入,然后,枚举结果。就如下面的示例:

 

let contactsTable =

  execCommand

    "MyConnection"

    "select * from Person.Contact"

 

for row in contactsTable do

  for col in row.Keys do

    printfn "%s = %O" col(row.Item(col))

 

 运行结果如下:

 

...

ContactID = 18

NameStyle = False

Title = Ms.

FirstName = Anna

MiddleName = A.

LastName = Albright

Suffix =

EmailAddress = anna0@adventure-works.com

EmailPromotion = 1

Phone = 197-555-0143

PasswordHash =6Hwr3vf9bo8CYMDbLuUt78TXCr182Vf8Zf0+uil0ANw=

PasswordSalt = SPfSr+w=

AdditionalContactInfo =

rowguid =b6e43a72-8f5f-4525-b4c0-ee84d764e86f

ModifiedDate = 01/07/2002 00:00:00

...

 

有一件事情必须注意,处理关系型数据库时,需要保证连接及时关闭。很快地关闭连接,可以使连接用于其他数据库用户,提高并行访问能力。我们看一下前面的例子,看它是如何创建连接,并自动清除的。在前面的例子中,开启者在每次使用Seq.iter 枚举集合时调用函数 openConnectionReader;它使用可枚举对象遍历数据,重复使用生产者函数产生单独的结果。每次调用 Seq.iter 就创建一个SqlConnection [ 不应该是SqlDataReader ]和 SqlDataReader 对象。在遍历结束时,或者由于某些原因遍历突然终止时,这些对象必须关闭。幸运的是,Seq.generate 函数能够在遍历完成或部分完成时,清除资源。当客户端完成枚举集合时,就调用关闭函数。应该使用这个函数在SqlDataReader上调用 IDisposable.Dispose 方法,触发关闭。还必须关闭对应的 SqlConnection 对象,它是由关闭 SqlDataReader、再关闭数据库连接而完成的。

 

command.ExecuteReader(CommandBehavior.CloseConnection)

 

为不使连接打开时间太长,应该在遍历可枚举集合的结果期间,避免复杂或过度耗时的操作,特别要避免用户与集合因某种原因的交互。例如,下面的代码重写了前面的例子,用户通过按回车键移动到下一条记录,这对数据库的性能是有害的:

 

for row in contactsTable do

  for col in row.Keys do

  printfn "%s = %O" col(row.Item(col))

  printfn "Press <enter> to see nextrecord"

  read_line() |> ignore

 

如果你想多次使用集合,或者想让用户交互,通常应该把它转换成列表或数组,就像下面的代码:

 

let contactsTable =

  execCommand

  "select * from Person.Contact"

  "MyConnection"

 

let contactsList = Seq.toList contactsTable

 

虽然当游标被垃圾回收时,连接将被关闭,但这个过程通常要花很长时间,特别是系统在高压力下。例如,如果写的代码运行服务器应用程序,处理大量并发用户,那么,不关闭连接将引起错误,因为服务器会很快用完数据库连接。