4绑定到业务逻辑
在上面GetProduct的定义时,可以看到该方法返回的类型是SqlDataReader,由于ObjectDataSource将来需要作为绑定控件的数据来源,所以它的返回类型必须如下的返回类型之一:
Ienumerable、DataTable、DataView、DataSet或者Object。
为了更好的进行业务处理,我们需要更进一步的封装业务逻辑,以便返回强类型。接下来我们定义一个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;
}
}
代码2-12 Product.cs源代码
然后在业务处理里定义ProductDB.cs类来进行处理,代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Web;
/// <summary>
/// Summary description for ProductDB
/// </summary>
public class ProductDB
{
public ProductDB()
{}
public List<Product> LoadAllProduct()
{
List<Product> products = new List<Product>();
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].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["ConnectionString"].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["ConnectionString"].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();
}
}
代码2-13 ProductDB.cs源代码
在这段代码里使用了泛型,所以需要导入System.Collections.Generic;命名空间,编辑和删除传递的参数是Product类型。另外在Command命令参数的加入方式上使用的是Add,读者也可以想上面一样使用AddWithValue方法。
然后建立DB_ObjectDataSource.aspx页面,下面列出了ObjectDataSource设置如2-14
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="LoadAllProduct"
DataObjectTypeName="Product" TypeName="ProductDB" DeleteMethod="DeleteProduct" UpdateMethod="UpdateProduct"></asp:ObjectDataSource>
代码2-14 DB_ObjectDataSource.aspx部分代码
在这段代码里使用了DataObjectTypeName属性,并将改值设置为Product类。运行结果和图3-34一样。
5 DataKeyNames和OldValuesParameterFormatString
1)DataKeyNames
如果读者使用Simple_ObjectDataSource.aspx,可以发现如果没有设置GridView的DataKeynames属性,则无法更新或者删除操作。
在 Update 和 Delete 操作中扮演特殊角色的一个重要属性是 DataKeyNames 属性。此属性通常被设置为数据源中字段的名称,这些字段是用于匹配该数据源中的给定行的主键的一部分。当以声明方式指定此属性时,多个键之间用逗号分隔,尽管通常情况下只有一个主键字段。
为了保留原始值以传递给 Update 或 Delete 操作,DataKeyNames 属性指定的字段的值在视图状态中往返,即使该字段并未作为 GridView 控件中的列之一被呈现。当 GridView 调用数据源 Update 或 Delete 操作时,它在一个特殊的 Keys 字典中将这些字段的值传递给数据源,该字典独立于包含用户在行处于编辑模式时(对于更新操作)输入的新值的 Values 字典。 Values 字典的内容是从为处于编辑模式的行呈现的输入控件中获得的。
例如,假设数据库里由如下一条记录
ProductID ProductName CategoryID Price InStore Description
24 生物技术 7 9.0000 2 生物技术丛书
为了进行数据传递,在实际执行该行的编辑时,ASP.NET框架将以Key/Value字典的形式进行存储的,这里我们将它写成如下的方式以便理解:
key value
{"@ProductID" , "24" }
{"@ProductName", "生物技术"}
{"@CategoryID", "7" }
{"@price", "9.0000"}
{"@InStore", "2" }
{"@Description", "生物技术丛书"}
然而我们知道在数据库里productID是递增的主键,所以在实际更新中,该行并不需要进行更新,若要排除此字典中的该字段,我们可以在GridView的绑定列中设置该列为只读。当将 Columns 集合中的对应 BoundField 的 ReadOnly 属性设置为 true时,该字段将不会在key/value里传递。另一方面还请注意,默认的如果在 Visual Studio 中使用 GridView 设计器,主键字段的 ReadOnly 属性会自动设置为 true。
由于我们在前面的演示里已经将ProductID设置为已经将ProductID设置为ReadOnly为true,自然的传递到UpdateProduct的Key/Value的值为:
{"@ProductName", "生物技术"}
{"@CategoryID", "7" }
{"@price", "9.0000"}
{"@InStore", "2" }
{"@Description", "生物技术丛书"}
这里可以看到没有了@ProductID列,那么系统如何知道你当前编辑的ProductID呢?这个功能就是由DataKeyNames来完成。原来当您将GridView的DataKeyNames设置为ProductID时,该列在更新时会自动调用ProductID的数值。
在删除方法DeleteProduct里,如果您设置断点查看pro的值,您会发现在删除产品里仅仅传递DataKeyname的值(也就是仅仅传递ProductID),而并不传递ProductName,CategoryID等的值,所以您会发现,对于编辑我定义的方式是:
public Product(int productID, string productName, int categoryID, decimal price, int instore, string description)
对于删除,我定义的方式是
public void DeleteProduct(int ProductId)
就是这个原因。
2)OldValuesParameterFormatString
在使用前面的例子里,请注意分配给 UpdateCommand 的 Update 语句中的参数的命名约定。Update和Delete的参数都采用默认的列命名方式,例如ProductDAL.cs里的DeleteProduct定义如下:
public void DeleteProduct(int ProductId)
{
SqlConnection con = new SqlConnection(_connectionString);
string deleteString = "DELETE FROM Products WHERE ProductID=@ProductID";
SqlCommand cmd = new SqlCommand(deleteString, con);
cmd.Parameters.AddWithValue("@ProductID", ProductId);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
如果你想更改列的名称,例如更改DeleteProduct如下
public void DeleteProduct(int old_ProductId)
{
SqlConnection con = new SqlConnection(_connectionString);
string deleteString = "DELETE FROM Products WHERE ProductID=@ProductID";
SqlCommand cmd = new SqlCommand(deleteString, con);
cmd.Parameters.AddWithValue("@ProductID", old_ProductId);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
那么你在运行时将出现错误如图2-34

图2-34 参数不匹配错误
这是因为GridView 和其他数据绑定控件调用 Update 操作的自动功能需依赖此命名约定才能工作。参数的命名预期应与 SelectCommand 返回的关联字段值相同。使用此命名约定使得数据绑定控件传递给数据源的值与 SQL Update 语句中的参数相对应成为可能。
此默认命名约定的使用假设 Keys 和 Values 字典的内容相互排斥 -- 即用户能够在数据绑定控件处于编辑模式时更新的字段值的命名应该与用于匹配要更新的行的字段值(对于 SqlDataSource,这些字段值在 WHERE 子句中)的命名不同。考虑这点的另一种方式是在 DataKeyNames 上设置的任何字段都应该设置为只读或在数据绑定控件中(例如在 GridView Columns 集合中)不可见。虽然键字段为只读的情况很普遍,但是存在一些有效的方案,其中您将希望能够更新同时还用于匹配要更新的数据行的字段。
例如,如果我们将Products数据库的ProductID列在设计表格结构时设置为nvarchar,它存放的是图书ISDN编号,该编号并不是递增的,因此在运行时,您可以更改ProductID的只,前提是主要不重复即可。
这样我们就需要将该ProductID列设置为ReadOnly=”false”以便允许编辑,另一方面,为了确认哪条记录被更新还需要传递该列的只到更新/删除方法,所以还需要将DataKeyNames设置为ProductID。
这样GridView 将在 Keys 字典中传递该字段的旧值,而在 Values 字典中传递该字段的新值。仍以UpdateProduct为例,当将ProductID的ReadOnly设置为”false”,并且将DataKeyNames设置为ProductID后,对于前面介绍的这条记录
ProductID ProductName