在 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 控件以简化数据库访问。
ObjectDataSource 控件包含 4 个重要属性:SelectMethod 属性、UpdateMethod 属性、InsertMethod 属性和 DeleteMethod 属性。综合利用这些属性,您能够指定执行标准数据库操作所需的所有方法。
例子:
绑定到数据访问层
数据访问层组件封装 ADO.NET 代码以通过 SQL 命令查询和修改数据库。它通常提炼创建 ADO.NET 连接和命令的详细信息,并通过可使用适当的参数调用的方法公开这些详细信息。典型的数据访问层组件可按如下方式公开:
public class MyDataBllLayer {
public DataView GetRecords();
public int UpdateRecord(int recordID, String recordData);
public int DeleteRecord(int recordID);
public int InsertRecord(int recordID, String recordData);
}
通常是在业务逻辑访问层定义对数据库里记录的操作,上面就定义了GetRecords、UpdateRecord、DeleteRecord和InsertRecord四个方法来读取、更新、删除和插入数据库里的数据,这些方法基本上是根据SQL里的Select、Update、Delete和Insert语句而定义。
和上面方法相对应, ObjectDataSource提供了四个属性来设置该控件引用的数据处理,可以按照如下方式关联到该类型,代码如下
<asp:ObjectDataSource TypeName="MyDataLayer" runat="server"
SelectMethod="GetRecords"
UpdateMethod="UpdateRecord"
DeleteMethod="DeleteRecord"
InsertMethod="InsertRecord"
/>
这里的SelectMethon设置为MyDataBllLayer里的GetRecords()方法,在使用时需要注意ObjectDataSource旨在以声明的方式简化数据的开发,所以这里设置SelectMethod的值为GetRecords而不是GetRecords()。
同样依次类推,UpdateMethod/DeleteMethod/InsertMethod分别对应的是UpdateRecord
/DeleteRecord/InsertRecord方法。
在上面GetRecords()的定义时,可以看到该方法返回的类型是DataView,由于ObjectDataSource将来需要作为绑定控件的数据来源,所以它的返回类型必须如下的返回类型之一:
Ienumerable、DataTable、DataView、DataSet或者Object。
除此以外,ObjectDataSource还有一个重要的属性TypeName,ObjectDataSource控件使用反射技术来从来从业务逻辑程序层的类对象调用相应的方法,所以TypeName的属性值就是用来标识该控件工作时使用的类名称,下面的例子演示了ObjectDataSource的基本使用。
在该例子里定义了一个数据业务逻辑层来处理数据库链接和业务逻辑,这些处理都是有App_Code文件夹下的NorthwndDB.cs文件完成,先大致浏览一下该文件里定义的方法:
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Collections.Generic;
using System.Web.UI;
using System.Web.UI.WebControls;
public class EmployeeInfo
{
private string _connectionString;
public EmployeeInfo()
{ _connectionString =
ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
}
public SqlDataReader GetEmployees()
{
SqlConnection con = new SqlConnection(_connectionString);
string selectString = "SELECT EmployeeID,LastName,Firstname,Title,Address,City,Region,PostalCode FROM Employees ORDER BY EmployeeID";
SqlCommand cmd = new SqlCommand(selectString, con);
con.Open();
SqlDataReader dtr =
cmd.ExecuteReader(CommandBehavior.CloseConnection);
return dtr;
}
public void UpdateEmployee(int EmployeeId, string LastName, string Firstname, string Title, string Address, string City, String Region, string PostalCode)
{
SqlConnection con = new SqlConnection(_connectionString);
string updateString = "UPDATE Employees SET Address=@Address,City=@City WHERE EmployeeID=@EmployeeID";
SqlCommand cmd = new SqlCommand(updateString, con);
cmd.Parameters.AddWithValue("@Address", Address);
cmd.Parameters.AddWithValue("@City", City);
cmd.Parameters.AddWithValue("@EmployeeID", EmployeeId);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
public void DeleteEmployee(int EmployeeId)
{
SqlConnection con = new SqlConnection(_connectionString);
string deleteString = "DELETE Employees WHERE EmployeeID=@EmployeeID";
SqlCommand cmd = new SqlCommand(deleteString, con);
cmd.Parameters.AddWithValue("@EmployeeId", EmployeeId);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
在这段代码里,定义了GetEmployees方法来获取所有员工级别信息,UpdateEmployee方法更新员工的基本资料,DeleteEmployee方法用来删除员工资料,这样就可以在ObjectDataSource1.aspx里使用如下代码调用业务逻辑的处理,如下
<asp:GridView
ID="GridView1"
DataSourceID="ObjectDataSource1"
DataKeyNames="EmployeeId"
AutoGenerateColumns="true"
AutoGenerateEditButton="True"
AutoGenerateDeleteButton="True"
Runat="Server" CellPadding="4" Font-Names="Verdana" Font-Size="X-Small" ForeColor="#333333" GridLines="None">
… …
</asp:GridView>
<asp:ObjectDataSource
ID="ObjectDataSource1"
TypeName="EmployeeInfo"
SelectMethod="GetEmployees"
UpdateMethod="UpdateEmployee"
DeleteMethod="DeleteEmployee"
Runat="Server">
<UpdateParameters>
<asp:Parameter Name="EmployeeId" Type="Int32" />
<asp:Parameter Name="Address" />
<asp:Parameter Name="City" />
</UpdateParameters>
</asp:ObjectDataSource>
上面使用了GrieView控件后面会有专门介绍,这里我们专注ObjectDataSource控件的使用,在该控件里设置 TypeName为"EmployeeInfo"表示将来调用的类名称为EmployeeInfo,调用SelectMethond/UpdateMethod/DeleteMethod调用的的方法分别是EmployeeInfo类里的GetEmployees/UpdateEmployee/DeleteEmployee方法。
具体运行可以自己试验。从运行结果里单击“Edit”可以更新员工的资料,单击“Delete”将删除员工的资料。
在运行上面的例子里,可能看到UpdateEmployee的定义如下:
public void UpdateEmployee(int EmployeeId, string LastName, string Firstname, string Title, string Address, string City, String Region, string PostalCode)
{ ... ...
cmd.Parameters.AddWithValue("@Address", Address);
cmd.Parameters.AddWithValue("@City", City);
cmd.Parameters.AddWithValue("@EmployeeID", EmployeeId);
... ...
}
这段代码表示更新员工资料时,其实只更新了员工的地址(Address)和所在的程序(City),而对于姓名(FirstName,LastName)、职称(Title)等并没有更新。
既然FistName、LastName、Title等并没有更新,为什么不将UpdateEmployee写成如下方式呢?
public void UpdateEmployee(int EmployeeId string Address, string City,)
{ ... ...
cmd.Parameters.AddWithValue("@Address", Address);
cmd.Parameters.AddWithValue("@City", City);
cmd.Parameters.AddWithValue("@EmployeeID", EmployeeId);
... ...
}
也就是只保留需要的三个参数而将其它变量不写,如果这样做将发生错误如下:
提示错误为:ObjectDataSource1找不到一个包含Address,City,LastName,Firstname,Title,Region,PostalCode和Employee参数的非泛型方法(具体原因我还没有进行验证,我的感觉是ObjectDataSource是根据Select语句自动生成Delete/Insert/Update等的,所以在更新时,同样需要该参数,你可以到如下MSDN查看SelectMethod的说明:
http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.selectmethod(VS.80).aspx
)。
而我看到的例子,大多都是如下写法:
public void UpdateEmployee(int EmployeeId, string LastName, string Firstname, string Title, string Address, string City, String Region, string PostalCode)
{
UpdateEmployee(EmployeeId,Address,City)
}
public void UpdateEmployee(int EmployeeId, string Address, string City )
{
SqlConnection con = new SqlConnection(_connectionString);
string updateString = "UPDATE Employees SET Address=@Address,City=@City WHERE EmployeeID=@EmployeeID";
SqlCommand cmd = new SqlCommand(updateString, con);
cmd.Parameters.AddWithValue("@Address", Address);
cmd.Parameters.AddWithValue("@City", City);
cmd.Parameters.AddWithValue("@EmployeeID", EmployeeId);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
通过重载UpdateEmployee方法,便于数据的扩展和维护。
绑定到业务逻辑
为了更好的进行业务处理,需要进一步封装业务逻辑,以便返回强类型,举例定义一个Product来封装数据库,具体由Product.cs类来实现并放在App_Code目录.
using System;
public class Product
{
protected int _productID;
protected String _productName;
protected int _categoryID;
protected decimal _price;
protected int _inStore;
protected String _description;
public int ProductID
{
get { return _productID; }
set { _productID = value; }
}
public String ProductName
{
get { return _productName; }
set { _productName = value; }
}
public int CategoryID
{
get { return _categoryID; }
set { _categoryID = value; }
}
public decimal Price
{
get { return _price; }
set { _price = value; }
}
public int InStore
{
get { return _inStore; }
set { _inStore = value; }
}
public String Description
{
get { return _description; }
set { _description = value; }
}
public Product()
{ }
public Product(int productID, string productName, int categoryID, decimal price, int instore, string description)
{
this._productID = productID;
this._productName = productName;
this._categoryID = categoryID;
this._price = price;
this._inStore = instore;
this._description = description;
}
}
然后定义业务逻辑类ProductDB.cs来进行处理:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using System.Data.SqlClient;
/// <summary>
/// ProductDB 的摘要说明
/// </summary>
public class ProductDB
{
public ProductDB()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
public List<Product> GetProduct()
{
List<Product> products = new List<Product>();
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MyDatabase"].ConnectionString);
string commandText = "select * from Products";
SqlCommand command = new SqlCommand(commandText,conn);
conn.Open();
SqlDataReader dr = command.ExecuteReader();
while (dr.Read())
{
Product prod = new Product();
prod.ProductID = (int)dr["ProductID"];
prod.ProductName = (string)dr["ProductName"];
prod.CategoryID = (int)dr["CategoryID"];
prod.Price = (decimal)dr["price"];
prod.InStore = (Int16)dr["InStore"];
prod.Description = (String)dr["Description"];
products.Add(prod);
}
dr.Close();
conn.Close();
return products;
}
public void UpdateProduct(Product pro)
{
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MyDatabase"].ConnectionString);
SqlCommand updatecmd = new SqlCommand("UPDATE Products set ProductName=@ProductName,CategoryID=@CategoryID,Price=@Price,InStore=@InStore,Description=@Description where ProductID=@ProductID", conn);
updatecmd.Parameters.Add(new SqlParameter("@ProductName", pro.ProductName));
updatecmd.Parameters.Add(new SqlParameter("CategoryID", pro.CategoryID));
updatecmd.Parameters.Add(new SqlParameter("@Price", pro.Price));
updatecmd.Parameters.Add(new SqlParameter("@InStore", pro.InStore));
updatecmd.Parameters.Add(new SqlParameter("@Description", pro.Description));
updatecmd.Parameters.Add(new SqlParameter("@ProductID", pro.ProductID));
conn.Open();
updatecmd.ExecuteNonQuery();
conn.Close();
}
public void DeleteProduct(Product pro)
{
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MyDatabase"].ConnectionString);
SqlCommand delcmd = new SqlCommand("delete from Products where ProductID=@ProductID", conn);
delcmd.Parameters.Add(new SqlParameter("@ProductID", pro.ProductID));
conn.Open();
delcmd.ExecuteNonQuery();
conn.Close();
}
}
最后建立前台页面文件,添加objectdatasource控件显示数据
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DataObjectTypeName="Product"
DeleteMethod="DeleteProduct" SelectMethod="GetProduct" TypeName="ProductDB" UpdateMethod="UpdateProduct">
</asp:ObjectDataSource>
<asp:GridView ID="GridView1" runat="server" AutoGenerateDeleteButton="True" AutoGenerateEditButton="True"
CellPadding="4" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" Font-Names="Verdana"
Font-Size="XX-Small" ForeColor="#333333" GridLines="None">
<FooterStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" />
<RowStyle BackColor="#E3EAEB" />
<EditRowStyle BackColor="#7C6F57" />
<SelectedRowStyle BackColor="#C5BBAF" Font-Bold="True" ForeColor="#333333" />
<PagerStyle BackColor="#666666" ForeColor="White" HorizontalAlign="Center" />
<HeaderStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" />
<AlternatingRowStyle BackColor="White" />
</asp:GridView>