很多人问我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