SqlDataAdapter myDataAdapter = new SqlDataAdapter( " StoredProcedureName " ,myConnection); myDataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure; myDataAdapter.SelectCommand.Parameters.Add( " @sqlstr " ,sqlstr); DataSet ds = new DataSet(); myDataAdapter.Fill(ds); return ds; ds.Tables[ 0 ],ds.Tables[ 1 ],ds.Tables[ 2 ],分别对应三个结果集
string connstr = " server=(local);database=northwind;integrated security=true; " ; SqlConnection conn = new SqlConnection(connstr); conn.Open(); SqlCommand cmd1 = new SqlCommand( " select * from customers " , conn); SqlCommand cmd2 = new SqlCommand( " select * from orders " , conn); SqlDataReader rdr1 = cmd1.ExecuteReader(); // next statement causes an error prior to SQL Server 2005 SqlDataReader rdr2 = cmd2.ExecuteReader(); // now you can reader from rdr1 and rdr2 at the same time.
三:异步执行Command命令
在 ADO.NET 2.0 以前,通过 Command 类(如 SqlCommand、OleDbCommand等)执行 SQL
命令的线程一定要停下来等待执行结果。ADO.NET 2.0 新增了异步程序访问接口(asynchronous API),让线程发出命令后可以继续执行接下去的程序代码。
而在 ADO.NET 2.0 当前的版本只有 SqlClient 支持异步程序访问接口。
以往编写程序时,我们可以直接通过.NET Framework 所提供的多线程机制,或是以 Delegate 类包装多线程的方式,在 .NET Framework 所提供的异步架构下,设计调用执行 Command 对象实例。这些方法都是让一条工作线程(Worker Thread)停止在后台中等待执行结果,一旦有结果后,工作线程再通过标准的机制告知结果。
原本 ADO.NET 的 Command 对象执行 SQL 语法的方法有
ExecuteReader、ExecuteNonQuery、ExecuteXmlReader 以及 ExecuteScalar 等,搭配 .NET
Framework 原来就提供的异步模型惯例,除了 ExecuteScalar 方法外,其余的方法都新增了以 Begin 和 End 关键字开始的一对方法。也就是说 ExecuteReader 方法是同步执行,若要以异步的方式执行相同的功能,则调用 BeginExecuteReader 和 EndExecuteReader 这一组方法。在 .NET Framework 中,以 Begin 为字首的方法负责传入同名方法所需的参数,而以 End
为字首的方法用来取回执行结果,
例如某个方法的定义如下:
public override int ExecuteNonQuery()
则以异步调用的起始方法定义如下:
public IAsyncResult BeginExecuteNonQuery(AsyncCallback callback, object stateObject)
Begin~ 系列的方法会多加存放回调方法(Delegation)的指针参数,也就是上述语法中的 callback 参数。并提供语法中的 stateObject参数,让你设置想要带到 End~ 对应方法的信息。而 Begin~ 系列方法最后返回的是代表异步执行状态的 IAsyncResult 对象实例,而不是原本同步执行方法的返回结果,你可以藉此查询异步执行的状况。
而获得执行结果的方法定义如下:
public int EndExecuteNonQuery(IAsyncResult asyncResult)
在调用与 Begin~ 对应的 End~ 方法时,需要带入 Begin~ 方法所返回的 IAsyncResult
对象实例。异步执行完毕后,取回与原先同步执行方法相同的执行结果。
由于我们在执行完 Command 对象访问数据库的方法后,都会返回对象,如 ExecuteReader 取回 DataReader实例;ExecuteNonQuery 取回受影响的记录条数;ExecuteXmlReader 取回 XmlReader 实例。因此大概都需要通过End系列方法来获得执行结果,否则这些结果就遗失在系统中。
若要异步执行 Command 命令,另一个必需设置的是:数据库连接字符串内要加上 async=true 属性。若连接字符串没有加上该属性,而通过 Command对象实例调用异步执行的方法,则会产生异常(exception)。若 Command 通过连接执行时,重头到尾都是以同步的方式执行,则依照默认 async=false 的方式设置比较节省资源。若某些命令需要同步执行,另一些需要异步执行,则可以考虑使用不同的连接。
在介绍范例应用程序前,我们先稍微谈一下 .NET Framework 所提供的公共的异步运行应用程序设计模式,不只是 ADO.NET2.0,在其他访问耗时的程序编写上,也都可以套用这个模式。
.NET Framework内置了让应用程序异步运行的功能,让你在编写应用程序时,不会因为某些耗时等待的操作让程序停止响应,操作界面停滞让用户感觉起来好像死机一样。一般会以多线程的方式处理这种需求,但若你不熟悉线程的运行,或是想利用线程池(Thread Pool)的好处,都可以在较为耗时的操作上,采用 .NET Framework 所提供的异步功能。
一般来说文件 I/O、网络访问乃至于 Web Services 访问,以及本节所讨论的 DB 访问等都较为耗时,.NET Framework为这一类的类都提供了上述以 Begin~/End~开头的非同步执行方法,而这些方法皆成对出现。当然,也有可能是自己编写的方法其商业逻辑非常复杂,导致调用该方法后,需要等待一段时间来完成,这时还可以通过 .NETFramework 所提供的委托(Delegate)类来创建异步运行。
但是实际在我们的应用中,
但我们不需要获知DB服务器的返回信息时,我们推荐使用委托,尤其是在Web开发中。
因为在页面线程启动异步数据库访问时,当页面业务执行完毕后仍然无法放开访问数据库的异步线程。这是我们不希望看到的,但是使用委托却可以避免这个麻烦(webservice异步应用中一样如此)。
四:使用SqlBulkCopy批量装载数据(仅限SqlClient)
以往访问 SQL Server 2000 时,若有大量的数据记录需要添加到数据库内,例如从主机系统或是 NCR Teradata、Oracle等数据库系统下载大量数据记录,我们想要将它们快速添加到 SQL Server 2000中,可以有的选择是调用 T-SQL 的 Bulk Insert 语法、通过Linked Server 执行 SELECT INTO 语法或是执行 bcp.exe 工具程序,以及通过 DTS 的 Bulk Insert Task 或启动Transform Data Task 的快速装载(Use Fast Load)设置。
但若要通过自行编写的程序完成批次装载,只能以 C/C++ 调用 OLEDB 或 ODBC 的 Bulk API,无法通过 ADO.NET 或 ADO 等对象来执行。
ADO.NET 2.0 的 SqlClient 提供了一个新的类称为 SqlBulkCopy,它让 DataSet 内大量的数据或是 DataReader通过数据流(Stream)直接读取大量的记录,可以快速将这些记录添加到目的数据库的数据表中。但要注意的是它并非如我们一般用的 bcp.exe工具程序,可以从某个符号分隔文件读取大量数据,选择性地搭配格式文件(Format File)将记录装载到数据库中,或是将数据库内的数据导出成为一个文件。但由于DataSet 能集成 XML 数据,因此依然可以采用 SqlBulkCopy 类型,轻松地通过 DataSet 将 XML 文件数据大量转入到数据库。
可以利用SqlBulkCopy类快速写入大批量数据,针对SQL Server的优化,可以写入DataRow数据,DataTable,DataReader,并且可以映射不同的数据列名
WriteToServer(DataTable)写入数据表
WriteToServer(DataRow[])批次写入数据行
WriteToServer(DataTable ,DataRowState)按行状态写入数据库表
WriteToServer(IDataReader)写入DataReader对象
下面是个示例:
using (SqlConnection sqlcon = new SqlConnection( " Data Source=192.168.80.242;user id=oa;password=oapassword;initial catalog=test " )) { sqlcon.Open(); using (SqlBulkCopy bcp = new SqlBulkCopy(sqlcon)) { bcp.BulkCopyTimeout = 3000 ; bcp.DestinationTableName = " dbo.Test01 " ; bcp.ColumnMappings.Add( " id " , " id " ); bcp.ColumnMappings.Add( " name1 " , " name1 " ); bcp.ColumnMappings.Add( " name2 " , " name2 " ); bcp.ColumnMappings.Add( " name3 " , " name3 " ); // 映射到不同名列 bcp.ColumnMappings.Add( " changedname4 " , " name4 " ); bcp.WriteToServer(dt); sqlcon.Close(); } }
但是SqlBulkCopy使用时要注意以下几点:
1 确认确实需要大容量更新在执行此操作,(几十行的数据请尽量使用别的渠道把).
2 确认数据一致性,与检查机制,以免遇到主键冲突,数据不符格式等意外。
3 SqlBulkCopy操作可能会导致对目标表元数据的更改(例如,禁用约束检查时)。如果出现这种情况,访问大容量插入表的并发快照隔离事务将失败。
4 SqlBulkCopy将向数据库下大容量更新锁,请注意并发性,以免其他连接因长时间等待而超时。
五:DataSet的性能提升
对于开发人员来说,ADO.NET 2.0最激动人心的变化莫过于.net开发组终于实现了他们许诺多年的事情:确实提升Dataset的性能了。
由于1.1版本Dataset令人不敢恭维的性能使得Dataset许多方面被其性能问题而掩盖。
现在,在大幅度提升了Dataset的性能后。Dataset终于能日趋完美了。
提升是多方面的,被提升的方面包括下面几块:
1. 索引引擎被大大的提升
在对ADO.NET 2.0的Dataset作了相当数目的测试后,微软终于宣布Dataset2.0的数据访问能力获得极大的提高,广泛的数字是增加44倍!!!而且不像1.1中排序的陡峭曲线,2.0中的排序尽量做到了线型递增!
真不知道.net小组是以前做的实在太烂了,还是有了新的狠招被发明了。^_^
2. 二进序列制化的Dataset
Dataset有个好属性是支持序列化,但是有很多人对其提出了批评。不是这些人不喜欢Dataset支持序列化,而是序列化后的Dataset真的是太肥了。大量的
<xs:element name=””,type…占用了大量的空间以至让人难以忍受。
现在我们只需要设置 RemotingFormat 属性为SerializationFormat.Binary(默认是SerializationFormat.XML),则在序列化时完全采用二进制的数据格式,如此数据较小,因而较有效率。(官方的观点是缩为SerializationFormat.XML的1/4)。
六:DataTable和其他方面的性能提升
另一个好消息是DataTable的功能被大大增强了。毕竟我们不是时时需要Dataset。
1.装载XML数据
在 ADO.NET 1.* 时,离线的数据访问模型以 DataSet 对象为主,因此若要将 XML 的数据装载到 DataTable,必须通过 DataSet 来实现。若我们仅操作一个数据表,不需要访问多个数据表,则还需经过 DataSet 类才能赋予 DataTable 数据或将数据输出成 XML
文件,其过程有点繁琐。ADO.NET 2.0 的 DataTable 类则新增了与 DataSet 相同的
ReadXML、ReadXMLSchema、WriteXML 以及WriteXMLSchema 等方法。因此我们可以直接操作 DataTable 实例,而不需先创建 DataSet 类的实例来赋予 DataTable 实例数据,然后只使用 DataTable 实例。
2.结果集直接装载DataTable
可以通过DataTableReader对象生成DataTable和DataSet。利用DataTable和DataSet在2.0版本中新引入的方法Load,可以传递DataTableReader或者任何实现IDataReader接口的类对象。下面的代码就是通过Load方法将dt1的数据传递到新的数据表dt2中:
DataTableReader dtRdr = dt1.CreateDataReader(); DataTable dt2 = new DataTable(); dt2.Load(dtRdr);
在使用Load方法装载多行数据时,可以先调用BeginLoadData方法来避免通知 (notifications),索引维护(index maintenance)以及约束检查(constraint checking),然后再通过EndLoadData方法返回数据。
3.通过 SqlDataAdapter 类实例将 DataTable 内的记录更新回数据源
现在我们还可以使用 SqlDataAdapter 类实例通过 DataGridView 更新的记录。
Update方法由以下几个重载:
public int Update(DataRow[] dataRows); public override int Update(DataSet dataSet); public int Update(DataTable dataTable); public int Update(DataSet dataSet, string srcTable);
4.千呼万唤的Merge方法,终于实现了。
public void Merge(DataTable table); public void Merge(DataTable table, bool preserveChanges); public void Merge(DataTable table, bool preserveChanges, MissingSchemaAction missingSchemaAction);
preserveChanges:参数决定当前 DataTable在相同主键记录合并时,是保留数据表内当前更新过的记录(设置为 True),还是以合并来的记录覆盖掉曾经修改过的记录(设置为 False)。
5.轻量级对象和快速遍历
ADO.NET 2.0中的DataTable提供了CreateDataReader方法(在之前的版本名为GetDataReader),该方法将创建一个 DataTableReader对象。DataTableReader与DataTable不同,它是一个轻量级的对象,其支持 Disconnected,这一点与DataReader(SqlDataReader)不同。这些特点决定遍历DataTableReader对象将更加快速,占用的数据资源更少(Disconnected)。下面的代码创建了一个DataTableReader对象,并将其绑定到 DataGridView控件上:
using (SqlConnection cn = new SqlConnection(cnStr)) { SqlCommand cmd = new SqlCommand(sqlAllCustomers, cn); SqlDataAdapter adpt = new SqlDataAdapter(cmd); DataTable dtCustomers = new DataTable( " Customers " ); adpt.Fill(dtCustomers); DataTableReader dtRdr = ds.CreateDataReader(); dgvCustomers.DataSource = dtRdr; }