很多人问我
TableAdapter
是否能够从存储过程里读取多组数据结果。最直接的回答是:不能。你不能通过
TableAdapter.Fill()
方法来得到一个
Dataset
。但是我们可以通过另一种简单的方法来实现。
DataAdapter.Fill()和多组数据结果
TableAdapter.Fill()
方法通过调用
DataAdapter.Fill()
从数据库中读取数据。
DataSet.Fill()
方法可以从存储过程里读取多组数据结果。为了获得多组数据结果,可以应用
DataAdapter.Fill()
的一个重载方法,它将
Dataset
作为参数,这样就可以把存储过程的多组数据结果返回给包含有多个表的
Dataset
。
这里,我们通过一个简单的示例来演示一下这种方法是怎样实现的:
假设在
Northwind
数据库里有一个存储过程
dbo.spSelectCustomersOrders
,
CREATE
PROCEDURE spSelectCustomersOrders
AS
BEGIN
SET NOCOUNT ON;
SELECT * FROM Customers
SELECT * FROM Orders
END
GO
AS
BEGIN
SET NOCOUNT ON;
SELECT * FROM Customers
SELECT * FROM Orders
END
GO
下面的代码调用了这个存储过程,并且把
2
组数据结果存储在
Dataset
里。
Dim
myConn As New System.Data.SqlClient.SqlConnection
Dim myAdapter As New System.Data.SqlClient.SqlDataAdapter
Dim mySelectCommand As New System.Data.SqlClient.SqlCommand
Dim myDataset As New System.Data.DataSet
myConn.ConnectionString = "Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True"
mySelectCommand.Connection = myConn
mySelectCommand.CommandText = "dbo.spSelectCustomersOrders"
myAdapter.SelectCommand = mySelectCommand
myAdapter.Fill(myDataset)
For Each table As System.Data.DataTable In myDataset.Tables
Console.WriteLine("Table Name:" & table.TableName)
Next
Dim myAdapter As New System.Data.SqlClient.SqlDataAdapter
Dim mySelectCommand As New System.Data.SqlClient.SqlCommand
Dim myDataset As New System.Data.DataSet
myConn.ConnectionString = "Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True"
mySelectCommand.Connection = myConn
mySelectCommand.CommandText = "dbo.spSelectCustomersOrders"
myAdapter.SelectCommand = mySelectCommand
myAdapter.Fill(myDataset)
For Each table As System.Data.DataTable In myDataset.Tables
Console.WriteLine("Table Name:" & table.TableName)
Next
代码的输出形式如下
:
Table Name: Table
Table Name: Table1
Table Name: Table1
我们可以看到,
DataAdapter.Fill()
方法执行了存储过程,并且把
2
组数据结果分别存储在
2
个数据表里。
TableAdapter的解决方案
然而,为什么
TableAdapter.Fill()
方法不能够正确地处理多组数据结果?那是因为
TableAdapter.Fill()
调用的
DataAdapter.Fill()
方法是以
DataTable
作为参数,而不是
Dataset
。这种情况,我们只需要在
TableAdapter
里创建一个新的
Fill
方法,令其调用以
Dataset
为参数的
DataAdapter.Fill()
方法。
假设这里有一个包含
Customers
和
Orders
的
NorthwindDataset.xsd
文件。让我们用上面的存储过程来实现新的
Fill
方法。把下面的代码加到
partial class
文件里。(在
Dataset Designer
上,可以通过双击或者右键选择
"View Code"
来进入
partial class
,当然也可以手动创建一个空的
class
文件。)
Namespace
NorthwindDataSetTableAdapters
Partial Public Class CustomersTableAdapter
Public Function FillCustomersOrders(ByVal dataSet As NorthwindDataSet) As Integer
Dim multiSelectCommand As New System.Data.SqlClient.SqlCommand
Dim returnValue As Integer
multiSelectCommand.Connection = Me.Connection
multiSelectCommand.CommandText = "dbo.spSelectCustomersOrders"
Me.Adapter.SelectCommand = multiSelectCommand
'' Map auto-created Table1 that holds the second result-set (Orders rows) to
'' Orders DataTable in our Dataset.
Me.Adapter.TableMappings.Add("Table1", "Orders")
returnValue = Me.Adapter.Fill(dataSet)
Return returnValue
End Function
End Class
End Namespace
Partial Public Class CustomersTableAdapter
Public Function FillCustomersOrders(ByVal dataSet As NorthwindDataSet) As Integer
Dim multiSelectCommand As New System.Data.SqlClient.SqlCommand
Dim returnValue As Integer
multiSelectCommand.Connection = Me.Connection
multiSelectCommand.CommandText = "dbo.spSelectCustomersOrders"
Me.Adapter.SelectCommand = multiSelectCommand
'' Map auto-created Table1 that holds the second result-set (Orders rows) to
'' Orders DataTable in our Dataset.
Me.Adapter.TableMappings.Add("Table1", "Orders")
returnValue = Me.Adapter.Fill(dataSet)
Return returnValue
End Function
End Class
End Namespace
有两点需要特别注意:
首先,新的
FillCustomersOrders
是以
Dataset
为参数,这样当我们调用
DataAdapter.Fill()
方法时,数据结果就会准确地存储到
Dataset
里。
第二,注意我们是怎样应用
TableMapping
将自动生成的数据表映射到
Dataset
里的
Orders
表。当应用
DataAdapter.Fill()
来读取多组数据结果,每一组数据结果都被单独地存储在
Dataset
的数据表里。默认情况下,这些数据表被命名为
Table, Table1, Table2…
,为了将这些数据标与
Dataset
里定义的数据表相对应,我们应用
TableMapping
。如果你打开
NorthwindDataset.xsd
后面的代码,在
TableAdapter class
的
InitAdapter()
方法,你就会看到类似的代码:
tableMapping.SourceTable = "Table"
tableMapping.DataSetTable = "Customers"
'' Colum mapping code skipped
...
Me._adapter.TableMappings.Add(tableMapping)
tableMapping.DataSetTable = "Customers"
'' Colum mapping code skipped
...
Me._adapter.TableMappings.Add(tableMapping)
这段代码是为了保证
DataAdapter.Fill
方法返回的数据表与
Dataset
里的数据表相对应。在我们
FillCustomersOrders
示例里,第二组结果包含的是
Orders
信息,所以我们在
Table1
和
Orders
之间创建了映射关系,确保数据
Fill
到
Orders
表中。
把以上代码添加到
partial class
后,你就可以调用
FillCustomersOrders
方法来
fill Customers
和
Orders
。
CustomersTableAdapter.FillCustomersOrders(Me.NorthwindDataSet)
性能的考虑
有些情况下,这种方法的确很有效。但是这也要看情况,也许你会想到这个方法可以避免多次访问数据库,从而提高性能,但如果仅仅只需要获取一小部分数据,却应用这种方法一次读取了大量的数据,这同样也会降低性能,倒不如一次读取小部分数据,需要其它数据时,与数据库建立另一个连接,再读取。
ADO.NET
在处理多个数据库连接方面性能优化得还是不错的,很多情况下,都不至于导致性能瓶颈。总之我们只需要遵循最基本的原则:
只在需要的时候,才去读取数据。
但有些情况下,读取多组数据结果还是很有帮助的,所以,应用我在这里所介绍的方法吧,但时刻也不要忘记性能的问题。
相关资源
- DataAdapter.Fill(DataSet): http://msdn2.microsoft.com/en-gb/library/377a8x4t.aspx
- DataTableMapping Class: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdatacommondatatablemappingclasstopic.asp
- How to: Extend the Functionality of a Dataset: http://msdn2.microsoft.com/en-us/library/ms171896(VS.80).aspx