Stephen Walther
Superexpert
适用于:
Microsoft ASP.NET 2.0
Microsoft Visual Studio 2005
摘要 :ADO.NET 和 SqlDataSource 使得人们可以很容易地访问 ASP.NET 2.0 中的两层数据。但是,它们在 n 层应用程序中就不是那么有效了,而 ObjectDataSource 却能在 n 层应用程序中为业务对象提供相同的易用性。学习如何使用 ASP.NET 2.0 Framework 并利用 ObjectDataSource 控件生成严格意义上的多层 Web 应用程序。
简介
使用 ObjectDataSource 控件显示数据
结合使用参数与 ObjectDataSource 控件
使用 ObjectDataSource 控件编辑数据
结合使用 ObjectDataSource 控件和 SqlDataSource 控件
小结
在 Microsoft ASP.NET 2.0 Framework 中,数据库访问得到了极大的简化。利用全新的 SqlDataSource 控件,您无需编写一行代码就可以选择、更新、插入和删除数据库数据。
生成简单的应用程序时,SqlDataSource 控件是一个很好的选择。如果您需要迅速生成一个使用户可以显示和编辑数据库记录的 Web 页,使用 SqlDataSource 控件在几分钟之内就能完成此工作。
例如,我自己就曾计时生成了这么一个页面。通过结合使用 SqlDataSource 控件与 GridView 控件,我在 1 分 15秒 内就能生成一个用于显示 Northwind Products 数据库表的内容的页面。就有这么快!
但是,SqlDataSource 控件存在一个问题。如果您使用 SqlDataSource 控件,那您就是在做不太妙的事情。SqlDataSource 控件的缺点在于它迫使您将用户界面层与业务逻辑层混合在一起。任何应用程序架构师都会告诉您:混合多个层的行为是不可取的。
生成严格意义上的多层 Web 应用程序时,您应该具有清晰的用户界面层、业务逻辑层和数据访问层。仅仅由于 SqlDataSource 控件的强制而在用户界面层引用 SQL 语句或存储过程是完全错误的。
那么为什么您要关心这些东西呢?不错,在很多情况下,您不必在意。如果您正在创建一个简单的 Web 应用程序,完全可以使用 SqlDataSource 控件。例如,如果您需要生成一个由单独页面组成的应用程序来显示数据库的表的内容,那么将应用程序划分为多个应用程序层就很不明智。
遗憾的是(如果您已经为此“交过学费”,则会感到幸运),并非所有的 Web 应用程序都很简单。应用程序达到一定的复杂程度之后,如果将其划分为多个应用程序层,则生成和维护它们就更轻松。
将 应用程序划分为多个应用程序层有很多优点。如果您有一个清晰的业务逻辑层,就能够创建一个可以从多个页面调用的方法库。换句话说,创建一个清晰的业务逻辑 层提升了代码重用。此外,创建清晰而独立的应用程序层使得应用程序更易于修改。例如,清晰的层次使您无需修改数据访问代码就可以修改用户界面。
如果您需要使用 ASP.NET Framework 生成多层 Web 应用程序,那么您可以使用 ASP.NET 2.0 Framework 所引入的另一个新控件:ObjectDataSource 控件ObjectDataSource 控件使您可将诸如 GridView 和 DropDownList 这样的用户界面控件绑定到一个中间层组件。
这篇文章的主题就是 ObjectDataSource 控件。在这篇文章中,您将学习如何使用此控件来显示和编辑数据库数据。我们还将讨论如何结合使用 ObjectDataSource 控件和 SqlDataSource 控件以简化数据库访问。
我们在这里设想您需要创建一个用于显示 Products 数据库表的内容的 Web 页面。再进一步设想您的某个现有业务组件包含了一种用于检索此数据的方法。
例如,清单 1 中的组件包含了一个名为 GetProducts 的方法,此方法返回一个 DataReader 来表示 Products 数据库表的内容。
清单 1: ProductInfo.cs (C#)
using System;
using System.Data;
using System.Data.SqlClient;
public class ProductInfo
{
const string conString =
"Server=localhost;Trusted_Connection=true;Database=Northwind";
public static SqlDataReader GetProducts()
{
SqlConnection con = new SqlConnection(conString);
string selectString = "SELECT * FROM Products";
SqlCommand cmd = new SqlCommand(selectString, con);
con.Open();
SqlDataReader dtr =
cmd.ExecuteReader(CommandBehavior.CloseConnection);
return dtr;
}
}
清单 1: ProductInfo.vb (Visual Basic .NET)
Imports System.Data
Imports System.Data.SqlClient
Public Class ProductInfo
Const conString As String = _
"Server=localhost;Trusted_Connection=true;Database=Northwind"
Public Function GetProducts() As SqlDataReader
Dim con As New SqlConnection(conString)
Dim selectString As String = "SELECT * FROM Products"
Dim cmd As New SqlCommand(selectString, con)
con.Open()
Dim dtr As SqlDataReader = _
cmd.ExecuteReader(CommandBehavior.CloseConnection)
Return dtr
End Function
End Class
如果您将清单 1 中包含的这个类添加到应用程序的 Code 目录中,那么 ASP.NET Framework 将自动编译这个类。换句话说,只要向 Code 目录添加了这个类,就可以立即在 ASP.NET 页中使用它。
我们将使用 GridView 控件(在 ASP.NET 2.0 Framework 中替换了 DataGrid 控件)来显示由 GetProducts 方法返回的数据库记录。清单 2 中的 ASP.NET 页包含了一个绑定到 ObjectDataSource 控件的 GridView 。
清单 2: ShowProducts.aspx
<html>
<head>
<title>Show Products</title>
</head>
<body>
<form id="form1" runat="server">
<asp:GridView
ID="GridView1"
DataSourceID="ObjectDataSource1"
Runat="Server" />
<asp:ObjectDataSource
ID="ObjectDataSource1"
TypeName="ProductInfo"
SelectMethod="GetProducts"
Runat="Server" />
</form>
</body>
</html>
清单 2 中声明的 ObjectDataSource 控件包含两个重要的属性。TypeName 属性指示类名,而 SelectMethod 属性指示在选择数据时要在此类上调用的方法名。
在清单 2 中,ObjectDataSource 控件用于调用 ProductInfo 类上的 GetProducts 方法。由于 GridView 控件绑定到了 ObjectDataSource 控件上,因此通过 GridView 控件的 DataSourceID 属性,GridView 控件即可显示产品列表(请参见图 1)。
您可以结合使用 ObjectDataSource 控件与任何标准的 ASP.NET 数据绑定控件(例如,GridView 、DropDownList 、TreeView 和 Repeater 控件)。ObjectDataSource 控件使您能够将任何标准控件绑定到组件。
SelectMethod 可以引用静态方法(在 Visual Basic .NET 中共享)或实例方法。如果您使用的是实例方法,则 ObjectDataSource 控件在调用这个方法前,会自动创建此组件的一个实例。在完成方法调用后,将自动销毁此组件。
您可以将参数与使用 ObjectDataSource 控件调用的方法一起使用。当您调用某方法时,如果需要将某些值(例如,控件属性或查询字符串的值)传递给此方法,则这种方式就非常有用。
在前一节中,我们使用 ObjectDataSource 控件创建了一个页面,用于显示来自 Products 数据库表的所有记录。在本节中,我们将修改此页面,以便允许用户从 DropDownList 控件(请参见图 2)选择产品类别。
清单 3 包含了修改后的 ProductInfo 组件。
清单 3: ProductInfo2.cs (C#)
using System;
using System.Data;
using System.Data.SqlClient;
public class ProductInfo2
{
const string conString =
"Server=localhost;Trusted_Connection=true;Database=Northwind";
public SqlDataReader GetProducts(string category)
{
SqlConnection con = new SqlConnection(conString);
string selectString = "SELECT Products.* " +
"FROM Products INNER JOIN Categories " +
"ON Products.CategoryID=Categories.CategoryId " +
"WHERE CategoryName=@CategoryName";
SqlCommand cmd = new SqlCommand(selectString, con);
cmd.Parameters.AddWithValue("@CategoryName", category);
con.Open();
SqlDataReader dtr =
cmd.ExecuteReader(CommandBehavior.CloseConnection);
return dtr;
}
}
清单 3: ProductInfo2.vb (Visual Basic .NET)
Imports System.Data
Imports System.Data.SqlClient
Public Class ProductInfo2
Const conString As String = _
"Server=localhost;Trusted_Connection=true;Database=Northwind"
Public Function GetProducts(ByVal category As String) _
As SqlDataReader
Dim con As New SqlConnection(conString)
Dim selectString As String = "SELECT Products.* " &
"FROM Products INNER JOIN Categories " & _
"ON Products.CategoryID=Categories.CategoryId " & _
"WHERE CategoryName=@CategoryName"
Dim cmd As New SqlCommand(selectString, con)
cmd.Parameters.AddWithValue("@CategoryName", category)
con.Open()
Dim dtr As SqlDataReader = _
cmd.ExecuteReader(CommandBehavior.CloseConnection)
Return dtr
End Function
End Class
清单 3 中经过修改的 ProductInfo 组件包含了一个经过修改的 GetProducts 方法,此方法包含了一个用于类别名的参数。这个参数用于限制从数据库返回的产品。
清单 4 包含了 DropDownList 、GridView 和ObjectDataSource 控件,使您可以选择要显示的不同类别的产品。
清单 4: ShowProducts2.aspx
<html>
<head>
<title>Show Products</title>
</head>
<body>
<form id="form1" runat="server">
<asp:DropDownList
id="DropCategories"
AutoPostBack="true"
Runat="Server">
<asp:ListItem Value="Beverages" />
<asp:ListItem Value="Seafood" />
</asp:DropDownList>
<br /><br />
<asp:GridView
ID="GridView1"
DataSourceID="ObjectDataSource1"
Runat="Server" />
<asp:ObjectDataSource
ID="ObjectDataSource1"
TypeName="ProductInfo2"
SelectMethod="GetProducts"
Runat="Server">
<SelectParameters>
<asp:ControlParameter
Name="category"
ControlID="DropCategories" />
</SelectParameters>
</asp:ObjectDataSource>
</form>
</body>
</html>
从清单 4 的 DropDownList 控件选择新类别时,GridView 控件将自动地只显示来自选定类别的产品。
请注意,清单 4 中的 ObjectDataSource 控件包含了一个 SelectParameters 元素。这个元素列出了调用由 ObjectDataSource 控件的 SelectMethod 属性指定的方法时使用的所有参数。在本例中,SelectedParameters 元素包含了一个名为 category 的单个参数。这个参数表示来自 DropCategories DropDownList 控件的 SelectedValue 属性的值。
ObjectDataSource 控件包含 4 个重要属性:SelectMethod 属性、UpdateMethod 属性、InsertMethod 属性和 DeleteMethod 属性。综合利用这些属性,您能够指定执行标准数据库操作所需的所有方法。
例如,您可以使用 ObjectDataSource 控件来编辑数据库数据。在清单 5 中,修改后的 ProductInfo 类包含了一个新的 UpdateProduct 和 DeleteProduct 方法。
清单 5: ProductInfo3.cs (C#)
using System;
using System.Data;
using System.Data.SqlClient;
public class ProductInfo3
{
const string conString =
"Server=localhost;Trusted_Connection=true;Database=Northwind";
public static SqlDataReader GetProducts()
{
SqlConnection con = new SqlConnection(conString);
string selectString = "SELECT ProductId,ProductName, " +
"UnitPrice FROM Products ORDER BY ProductId";
SqlCommand cmd = new SqlCommand(selectString, con);
con.Open();
SqlDataReader dtr =
cmd.ExecuteReader(CommandBehavior.CloseConnection);
return dtr;
}
public static void UpdateProduct(int original_productId,
string productName, decimal unitPrice)
{
SqlConnection con = new SqlConnection(conString);
string updateString = "UPDATE Products SET " +
"ProductName=@ProductName,UnitPrice=@UnitPrice " +
"WHERE ProductID=@ProductID";
SqlCommand cmd = new SqlCommand(updateString, con);
cmd.Parameters.AddWithValue("@ProductName", productName);
cmd.Parameters.AddWithValue("@UnitPrice", unitPrice);
cmd.Parameters.AddWithValue("@ProductId", original_productId);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
public static void DeleteProduct(int original_productId)
{
SqlConnection con = new SqlConnection(conString);
string deleteString = "DELETE Products " +
"WHERE ProductID=@ProductID";
SqlCommand cmd = new SqlCommand(deleteString, con);
cmd.Parameters.AddWithValue("@ProductId", original_productId);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
清单 5: ProductInfo3.vb (Visual Basic .NET)
Imports System.Data
Imports System.Data.SqlClient
Public Class ProductInfo3
Const conString As String = _
"Server=localhost;Trusted_Connection=true;Database=Northwind"
Public Shared Function GetProducts() As SqlDataReader
Dim con As New SqlConnection(conString)
Dim selectString As String = "SELECT ProductId, " & _
"ProductName,UnitPrice FROM Products ORDER BY ProductId"
Dim cmd As New SqlCommand(selectString, con)
con.Open()
Dim dtr As SqlDataReader = _
cmd.ExecuteReader(CommandBehavior.CloseConnection)
Return dtr
End Function
Public Shared Sub UpdateProduct(ByVal original_productId _
As Integer, ByVal productName As String, _
ByVal unitPrice As Decimal)
Dim con As New SqlConnection(conString)
Dim updateString As String = "UPDATE Products " & _
"SET ProductName=@ProductName,UnitPrice=@UnitPrice " & _
"WHERE ProductID=@ProductID"
Dim cmd As New SqlCommand(updateString, con)
cmd.Parameters.AddWithValue("@ProductName", productName)
cmd.Parameters.AddWithValue("@UnitPrice", unitPrice)
cmd.Parameters.AddWithValue("@ProductId", original_productId)
con.Open()
cmd.ExecuteNonQuery()
con.Close()
End Sub
Public Shared Sub DeleteProduct(ByVal original_productId _
As Integer)
Dim con As New SqlConnection(conString)
Dim deleteString As String = "DELETE Products " & _
"WHERE ProductID=@ProductID"
Dim cmd As New SqlCommand(deleteString, con)
cmd.Parameters.AddWithValue("@ProductId", original_productId)
con.Open()
cmd.ExecuteNonQuery()
con.Close()
End Sub
End Class
您可以将这个修改过的 ProductInfo 类与清单 6 中包含的ObjectDataSource 控件一起使用,以编辑 Products 数据库表的内容。
清单 6: ShowProducts3.aspx
<html>
<head>
<title>Show Products</title>
</head>
<body>
<form id="form1" runat="server">
<asp:GridView
ID="GridView1"
DataSourceID="ObjectDataSource1"
DataKeyNames="ProductId"
AutoGenerateColumns="false"
AutoGenerateEditButton="true"
AutoGenerateDeleteButton="true"
Runat="Server">
<Columns>
<asp:BoundField
DataField="ProductName"/>
<asp:BoundField
DataField="UnitPrice"
DataFormatString="{0:c}"/>
</Columns>
</asp:GridView>
<asp:ObjectDataSource
ID="ObjectDataSource1"
TypeName="ProductInfo3"
SelectMethod="GetProducts"
UpdateMethod="UpdateProduct"
DeleteMethod="DeleteProduct"
Runat="Server">
<UpdateParameters>
<asp:Parameter
Name="original_productId"
Type="Int32" />
<asp:Parameter
Name="productName" />
<asp:Parameter
Name="unitPrice"
Type="Decimal"/>
</UpdateParameters>
</asp:ObjectDataSource>
</form>
</body>
</html>
在清单 6 中,GridView 控件显示了ProductName 和 UnitPrice 这两个列的值。由于 GridView 控件的 AutoGenerateEditButton 和 AutoGenerateDeleteButton 属性均设置为值 True,GridView 将自动生成用于编辑和删除产品行的用户界面(请参见图 3)。
当您单击 Update 链接更新产品时,ObjectDataSource 控件将调用 UpdateProduct 方法。请注意,ObjectDataSource 控件在其 UpdateParameters 元素中列出了传递给 UpdateProduct 方法的参数。
有一个参数需要额外进行讨论。我们需要将被更新的行的 ProductID 列的值传递给 UpdateProduct 方法由于没有在 GridView 中显示 ProductID 列,我们必须将 ProductID 列分配给 GridView 控件的 DataKeyNames 属性。这个列的名称变为 original_productId,而不是 productId,因为我们正在向 update 方法传递 ProductID 列的未编辑版本。
如果您单击 Delete 链接,ObjectDataSource 控件将调用 DeleteProduct 方法。由于 GridView 控件的 DataKeyNames 属性具有值 ProductId,因此会再次将一个名为 original_productId 的参数自动传递给 DeleteProduct 方法。
到目前为止,我们已经将 ObjectDataSource 控件与使用 SqlDataReader 对象的组件一起使用,以便检索数据库数据。这里还有另一种选择。即不在组件内使用 SqlDataReader 或 DataSet 等 ADO.NET 对象,而是在组件中使用 SqlDataSource 控件。
您可以在组件中使用 SqlDataSource 控件的这一事实似乎很奇怪。通常,您不在组件中使用控件,因为控件一般具有可视化表示形式,而且控件参与页面执行生命周期。而 DataSource 控件在这一点上有些特殊。
如果您希望简化组件中的数据库访问代码,您可以在此组件中使用 SqlDataSource 控件。清单 7 展示了这种方式。
清单 7: ProductInfo4.cs (C#)
using System;
using System.Collections;
using System.Web.UI;
using System.Web.UI.WebControls;
public class ProductInfo4
{
const string conString =
"Server=localhost;Trusted_Connection=true;Database=Northwind";
public static IEnumerable GetProducts()
{
string selectString = "SELECT * FROM Products";
SqlDataSource dsrc = new SqlDataSource(conString,
selectString);
dsrc.DataSourceMode = SqlDataSourceMode.DataSet;
return dsrc.Select(DataSourceSelectArguments.Empty);
}
}
清单 7: ProductInfo4.vb (Visual Basic .NET)
Imports System.Collections
Imports System.Web.UI
Imports System.Web.UI.WebControls
Public Class ProductInfo4
Const conString As String = _
"Server=localhost;Trusted_Connection=true;Database=Northwind"
Public Shared Function GetProducts() As IEnumerable
Dim selectString As String = "SELECT * FROM Products"
Dim dsrc As New SqlDataSource(conString, selectString)
dsrc.DataSourceMode = SqlDataSourceMode.DataSet
Return dsrc.Select(DataSourceSelectArguments.Empty)
End Function
End Class
在清单 7 中,我们实例化了 SqlDataSource 控件的一个新实例。SqlDataSource 的构造函数接受了一个用于数据库连接字符串的参数和一个用于与 select 命令一起使用的命令文本的参数。接着,将 DataSourceMode 属性的值设为 DataSet(这里的另一个选项是 DataReader)。最后,在 SqlDataSource 控件的实例上调用 Select 方法,并返回表示来自 Products 数据库表的所有记录的 DataView 。
您可以将清单 7 中的 ProductInfo 类与清单 8 中包含的 ObjectDataSource 控件一起使用:
清单 8: ShowProducts4.aspx
<html>
<head>
<title>Show Products</title>
</head>
<body>
<form id="form1" runat="server">
<asp:GridView
ID="GridView1"
DataSourceID="ObjectDataSource1"
Runat="Server" />
<asp:ObjectDataSource
ID="ObjectDataSource1"
TypeName="ProductInfo4"
SelectMethod="GetProducts"
Runat="Server" />
</form>
</body>
</html>
在清单 8 中,GridView 控件绑定到了 ObjectDataSource 控件上。而 ObjectDataSource 控件又调用了 ProductInfo4 类的 GetProducts 方法,以检索由 GridView 显示的数据。最后,GetProducts 方法使用 SqlDataSource 控件检索数据库数据。
ASP.NET 2.0 Framework 极大地简化了数据库访问,使您可以更轻松地生成简单和复杂的 ASP.NET 应用程序。如果您需要生成一个简单的数据库驱动 Web 应用程序,那么您可以使用新的 SqlDataSource 控件。如果您需要生成一个更为复杂的应用程序或示例,您可以在传统的 3 层应用程序中使用 ObjectDataSource 控件。
ObjectDataSource 控件使您能够继续在数据驱动页面中使用中间层组件。其主要优势在于使您无需编写任何代码即可绑定到一个组件,从而极大简化了您的用户界面。使用 ObjectDataSource 控件,我可以在 3 分钟 20 秒的时间内生成一个可以显示 Product 数据库表的组件和页面。尽管这种方式花的时间比使用 SqlDataSource 控件长,但我感觉这样做时页面的体系结构要好得多。
关于作者
Stephen Walther 著有 ASP.NET方面最畅销的书�� ASP.NET Unleashed 。他还是 ASP.NET Community Starter Kit(Microsoft 公司生产的一个示例 ASP.NET 应用程序)的架构师和首席开发人员。他还通过自己的公司 Superexpert (http://www.superexpert.com ) 为美国的多家公司(包括 NASA 和 Microsoft)提供 ASP.NET 培训。