1 SqlDataSource 和 ObjectDataSource 控件的比较
ASP.NET2.0提供了
SqlDataSource
数据源控件,后者支持用于指定连接字符串、
SQL
语句或存储过程的属性,用以查询或修改数据库。但是,
SqlDataSource
控件存在一个问题:该控件的缺点在于它迫使您将用户界面层与业务逻辑层混合在一起。然而随着应用程序规模的扩大,您会越来越感觉到混合多个层的做法是不可取的。
生成严格意义上的多层
Web
应用程序时,您应该具有清晰的用户界面层、业务逻辑层和数据访问层。仅仅由于
SqlDataSource
控件的强制而在用户界面层引用
SQL
语句或存储过程是不可取的。
SqlDataSource
和
ObjectDataSource
的选择,从这某中意义上说,前者适合大多数小规模的个人或业余站点,而对于较大规模的企业级应用程序,在应用程序的呈现页中直接存储
SQL
语句可能很快就会变得无法维护。这些应用程序通常需要用中间层数据访问层或业务组件构成的封装性更好的数据模型。所以使用
ObjectDataSource
控件是一种较为明智和通用的做法。
2
ObjectDataSource
的概述
ObjectDataSource
控件对象模型类似于
SqlDataSource
控件。
ObjectDataSource
公开一个
TypeName
属性(而不是
ConnectionString
属性),该属性指定要实例化来执行数据操作的对象类型(类名)。类似于
SqlDataSource
的命令属性,
ObjectDataSource
控件支持诸如
SelectMethod
、
UpdateMethod
、
InsertMethod
和
DeleteMethod
的属性,用于指定要调用来执行这些数据操作的关联类型的方法。本节介绍一些方法,用于构建数据访问层和业务逻辑层组件并通过
ObjectDataSource
控件公开这些组件。
下面是该控件的声明方式:
<asp:ObjectDataSource
CacheDuration="string|Infinite" CacheExpirationPolicy="Absolute|Sliding"
CacheKeyDependency="string"
ConflictDetection="OverwriteChanges|CompareAllValues"
ConvertNullToDBNull="True|False" DataObjectTypeName="string"
DeleteMethod="string" EnableCaching="True|False"
EnablePaging="True|False" EnableTheming="True|False"
EnableViewState="True|False" FilterExpression="string"
ID="string" InsertMethod="string"
MaximumRowsParameterName="string"
OldValuesParameterFormatString="string"
OnDataBinding="DataBinding event handler"
OnDeleted="Deleted event handler" OnDeleting="Deleting event handler"
OnDisposed="Disposed event handler" OnFiltering="Filtering event handler"
OnInit="Init event handler" OnInserted="Inserted event handler"
OnInserting="Inserting event handler" OnLoad="Load event handler"
OnObjectCreated="ObjectCreated event handler"
OnObjectCreating="ObjectCreating event handler"
OnObjectDisposing="ObjectDisposing event handler"
OnPreRender="PreRender event handler" OnSelected="Selected event handler"
OnSelecting="Selecting event handler" OnUnload="Unload event handler"
OnUpdated="Updated event handler" OnUpdating="Updating event handler"
runat="server" SelectCountMethod="string"
SelectMethod="string" SortParameterName="string"
SqlCacheDependency="string" StartRowIndexParameterName="string"
TypeName="string" UpdateMethod="string"
>
<DeleteParameters>
<asp:ControlParameter ControlID="string"
ConvertEmptyStringToNull="True|False"
DefaultValue="string"
Direction="Input|Output|InputOutput|ReturnValue"
Name="string"
PropertyName="string"
Size="integer"
Type="Empty|Object|DBNull|Boolean|Char|SByte|
Byte|Int16|UInt16|Int32|UInt32|Int64|UInt64|
Single|Double|Decimal|DateTime|String"
/>
<asp:CookieParameter CookieName="string" />
<asp:FormParameter FormField="string" />
<asp:Parameter Name="string" />
<asp:ProfileParameter PropertyName="string" />
<asp:QueryStringParameter QueryStringField="string" />
<asp:SessionParameter SessionField="string" />
</DeleteParameters>
<FilterParameters>... ...</FilterParameters>
<InsertParameters>... ...</InsertParameters>
<SelectParameters>... ...</SelectParameters>
<UpdateParameters>... ...</UpdateParameters>
</asp:ObjectDataSource>
3
绑定到数据访问层
数据访问层组件封装
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
的属性值就是用来标识该控件工作时使用的类名称,下面通过
Simple_ObjectDataSource.aspx
来说明
ObjectDataSource
的基本使用。
1
)建立数据业务逻辑层
为了方装业务逻辑我建立了
ProductDAL.cs
文件。在该文件里定义了
GetProduct
方法获取产品列表,
UpdateProduct
方法更新产品记录,
DeleteProduct
删除产品记录,为了便于共享,将该文件放置在
App_Code
目录下,完整代码如1
-1
:
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 ProductBLL
/// </summary>
public class ProductDAL
{
protected int _count = -1;
public ProductDAL()
{ }
string _connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
public SqlDataReader GetProduct()
{
SqlConnection con = new SqlConnection(_connectionString);
string selectString = "SELECT * FROM Products";
SqlCommand cmd = new SqlCommand(selectString, con);
con.Open();
SqlDataReader dtr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
return dtr;
}
public void UpdateProduct(int productID, string productName, int categoryID, decimal price, Int16 inStore,string description)
{
SqlConnection con = new SqlConnection(_connectionString);
string updateString = "UPDATE Products set ProductName=@ProductName,CategoryID=@CategoryID,Price=@Price,InStore=@InStore,Description=@Description where ProductID=@ProductID";
SqlCommand cmd = new SqlCommand(updateString, con);
cmd.Parameters.AddWithValue("@ProductID",productID);
cmd.Parameters.AddWithValue("@ProductName",productName);
cmd.Parameters.AddWithValue("@CategoryID",categoryID);
cmd.Parameters.AddWithValue("@Price",price);
cmd.Parameters.AddWithValue("@InStore",inStore);
cmd.Parameters.AddWithValue("@Description",description);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
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();
}
}
代码1
-1 ProductDAL.cs
源文件
2
)建立表示层
建立一个页面
Simple_ObjectDataSource.aspx
然后将
ObjectDataSource
控件托方到
Web
窗体创,使用默认的
ID
。
Visual Stduio.NET2005
为我们建立业务逻辑提供了强大的支持。选中
ObjectDataSource1
,在其智能配置里选择配置数据源,弹出配置向导如图
2-1
。
图 2-1ObjectDataSource 配置向导
此时系统会枚举已经存在的类,选择
ProductDAL
,单击“
Next
”,进入“
Define Data Methods
”页面如图
2-2
。在此页面需要单独设置
Select
、
Update
、和
Delete
分别如下图
2-30 设置 Select 属性对应的方法 GetProduct
2-31 设置 Update 属性对应的方法 UpdateProduct
图 2-32 设置 Delete 属性对应的方法 DeleteProduct
通过上面的设置系统自动生成如下代码如下
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="DeleteProduct"
SelectMethod="GetProduct" TypeName="ProductDAL" UpdateMethod="UpdateProduct">
<DeleteParameters>
<asp:Parameter Name="ProductId" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="productID" Type="Int32" />
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="categoryID" Type="Int32" />
<asp:Parameter Name="price" Type="Decimal" />
<asp:Parameter Name="inStore" Type="Int16" />
<asp:Parameter Name="description" Type="String" />
</UpdateParameters>
</asp:ObjectDataSource>
代码
2-11 Simple_ObjectDataSource.aspx
部分源代码
图
2-33
显示了运行结果,此时我们可以编辑或者删除现有的产品记录。
图 2-33 Simple_ObjectDataSource.aspx 运行结果
注意:如果能够进行编辑、删除,你需要将GridView的DataKeyNames设置为数据库里的主键名。具体后面会说明。
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 CategoryID Price InStore Description
24
生物技术
7 9.0000 2
生物技术丛书
我想将
ProductID
更改为
ISBN001
,此时传递给
UpdateProduct
的方法是:
key value
{"24 " , "ISBN001" }
{"@ProductName", "
生物技术
"}
{"@CategoryID", "7" }
{"@price", "9.0000"}
{"@InStore", "2" }
{"@Description", "
生物技术丛书
"}
我们之所以需要这两个值是因为,利用“
24
”我们需要获取该记录,利用“
ISBN001
”我们需要知道将来要把
24
更新为什么值。因为这样我们就需要区分
Key
和
value
的值。为了区别这两类值,需要在
SQL
语句中以不同的方式命名参数,例如:
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:ConnectionString %>"
SelectCommand="SELECT [productid], [productname], [categoryID], [Price],[InStore],[Description] FROM [products]"
UpdateCommand="UPDATE [products] SET [productid] = @productid, [productname] = @productname, [categoryid] = @categoryid, [price] = @price,description=@Description WHERE [productid] = @old_productid"
DeleteCommand="DELETE FROM [products] WHERE [productid] = @ old_productid "/>
OldValuesParameterFormatString="old_{0}"
在上面例子中,参数名称
@old_productid
用于引用
Key
字段的原始值
24
,
@productid
用于引用新
Value
字段的新值
ISBN001
。其中旧值和新值的命名是通过
OldValuesParameterFormatString
来完成
。
SqlDataSource
的
OldValuesParameterFormatString
属性也被设置为有效的
.NET Framwork
格式字符串,以指示应该如何重命名
Keys
字典中的参数。当
SqlDataSource
的
ConflictDetection
属性设置为
CompareAllValues
时,此格式字符串还应用于数据绑定控件传递的非键字段的旧值。对于
Delete
操作,
SqlDataSource
默认仅应用
Keys
字典(不存在用于删除操作的新值),并使用
OldValuesParameterFormatString
属性的值格式化键参数名称。代码
2-14 FormatPara_ObjectDataSource.aspx
演示了上面的说明。
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="DeleteProduct"
SelectMethod="GetProduct" TypeName="ProductDAL" UpdateMethod="UpdateProduct"
OldValuesParameterFormatString="old_{0}">
</asp:ObjectDataSource>
<asp:GridView ID="GridView1" DataKeyNames="ProductID" runat="server" AutoGenerateDeleteButton="True"
AutoGenerateEditButton="True" CellPadding="4" DataSourceID="ObjectDataSource1" AutoGenerateColumns="false"
Font-Names="Verdana" Font-Size="XX-Small" ForeColor="#333333" GridLines="None"
>
<Columns>
<asp:BoundField DataField="ProductID" HeaderText="ProductID" ReadOnly="True" SortExpression="ProductID"/>
<asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
<asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" />
<asp:BoundField DataField="Price" HeaderText="Price" SortExpression="Price" />
<asp:BoundField DataField="InStore" HeaderText="InStore" SortExpression="InStore" />
<asp:BoundField DataField="Description" HeaderText="Description" SortExpression="Description" />
</Columns>
</asp:GridView>
代码
2-14FormatPara_ObjectDataSource.aspx
部分源代码
同时将
DeleteUpdate
方法改成代码
2-15
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-15 DeleteProduct
方法
上面代码的运行结果和图
2-33
一样。然而在使用上面代码时,可能有些人将
2-14
的代码写成如下的形式:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="DeleteProduct"
SelectMethod="GetProduct" TypeName="ProductDAL" UpdateMethod="UpdateProduct"
OldValuesParameterFormatString="old_{0}">
<DeleteParameters>
<asp:Parameter Name="ProductId" Type="Int32" />
</DeleteParameters>
</asp:ObjectDataSource>
如果运行此段代码则出现错误如图
2-35
,这是因为我们需要默认的参数
ProductID
,如果您显式设置则多此一举,系统认为你需要传递
ProductID
和
old_Product
。如果真的要设置应该设置
old_Product
,也就是
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="DeleteProduct"
SelectMethod="GetProduct" TypeName="ProductDAL" UpdateMethod="UpdateProduct"
OldValuesParameterFormatString="old_{0}">
<DeleteParameters>
<asp:Parameter Name="old_ProductId" Type="Int32" />
</DeleteParameters>
</asp:ObjectDataSource>
这种方法同样使用
UpdateMethod
。
2.3.5
冲突检测
ConflictDetection
正如在前面的主题中所提到的,数据绑定控件在单独的
Keys
、
Values
(新值)和
OldValues
字典中将值传递给数据源。默认情况下,
SqlDataSource
和
ObjectDataSource
忽略
OldValues
字典,仅应用
Keys
和
Values
。这种行为由数据源的
ConflictDetection
属性确定,该属性默认设置为
OverwriteChanges
。
OverwriteChanges
模式本质上意味着
“
仅为了更新或删除记录而匹配主键值
”
。这种行为意味着不管记录的基础值是否已更改,都要更新或删除该记录。通常,仅当行的值准确匹配最初选择的值时才允许
Update
或
Delete
成功是更为适宜的。这样,如果另一个用户在从您选择行到更新该行这段时间内更新了该行,您的更新操作将会失败。
以
Products
表为例,数据库里
ProductID
为
24
的图书在库数量(
InStore
)是两本,如下
ProductID ProductName CategoryID Price InStore Description
24
生物技术
7 9.0000 2
生物技术丛书
A
,
B
两个员工分别负责图书的入库和出库。在某个时间,
A
员工把新进的
5
本图书入库,所以它准备更改该记录在库数量为
7
本,而恰好同时有一读者购买了一本该书,员工
B
准备更新该记录在库为
1
本。在
A
员工正在更新而还没有更新的这段时间里,
B
也进行了该记录的更新,这样即使
A
更新了图书为
7
本由于
B
接着会将在库图书更新为
1
本而发生逻辑上错误。为了解决这个问题可以利用“全值匹配”。
数据源通过将
ConflictDetection
属性设置为
CompareAllValues
来支持这种方法。在这种模式下,数据源向命令或方法应用
OldValues
参数,该命令或方法可以在更新或删除记录之前使用这些值确保更新或删除操作匹配该记录的所有这些值。还必须将
OldValuesParameterFormatString
属性设置为有效的
.NET Framework
格式字符串(例如
“lodl_{0}”
),以指示应该如何重命名
OldValues
和
Keys
字典中的参数以将它们与
NewValues
进行区别。
下面的代码示例演示用于
SqlDataSource
控件的
OverwriteChanges
和
CompareAllValues
模式的典型
SQL
命令。
update
Products set ProductName=@ProductName,CategoryID=@CategoryID,
Price=@Price,InStore=@InStore,Description=@Description
WHERE (productID = @original_ProductID
AND ProductName=@original_ProductName
and CategoryID=@original_CategoryID
and price=@original_price
and InStore=@original_InStore
and description=@original_description)
代码
2-16
的
Conflict_ObjectDataSource.aspx
演示了这种功能的使用(注:为了便于理解,这里使用
SqlDataSource
进行说明)
<%@ Page Language="C#" %>
<script runat="server">
protected void SqlDataSource1_Updated(object sender, SqlDataSourceStatusEventArgs e)
{
if (e.AffectedRows == 0)
Response.Write("
该行已经变更,您更新失败
<br />");
}
protected void SqlDataSource1_Deleted(object sender, SqlDataSourceStatusEventArgs e)
{
if (e.AffectedRows == 0)
Response.Write("
改行已经变更,您删除失败
<br />");
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Optimistic Concurrency</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="SqlDataSource1"
ID="GridView1" runat="server" CellPadding="4" Font-Names="Verdana" Font-Size="XX-Small" ForeColor="#333333" GridLines="None">
<Columns>
<asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
<asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False"
ReadOnly="True" SortExpression="ProductID" />
<asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
<asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" />
<asp:BoundField DataField="Price" HeaderText="Price" SortExpression="Price" />
<asp:BoundField DataField="InStore" HeaderText="InStore" SortExpression="InStore" />
<asp:BoundField DataField="Description" HeaderText="Description" SortExpression="Description" />
</Columns>
… …
</asp:GridView>
<asp:SqlDataSource ConnectionString="<%$ ConnectionStrings:ConnectionString %>"
ID="SqlDataSource1"
ConflictDetection="CompareAllValues"
OldValuesParameterFormatString="original_{0}"
runat="server"
SelectCommand="GetAllProducts"
SelectCommandType="StoredProcedure"
InsertCommand="InsertProduct"
InsertCommandType="StoredProcedure"
DeleteCommand="DeleteProduct"
DeleteCommandType="StoredProcedure"
UpdateCommand="UpdateProduct"
UpdateCommandType="StoredProcedure"
OnUpdated="SqlDataSource1_Updated"
OnDeleted="SqlDataSource1_Deleted">
<InsertParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="CategoryID" Type="Int32" />
<asp:Parameter Name="Price" Type="decimal" />
<asp:Parameter Name="InStore" Type="Int16"/>
<asp:Parameter Name="Description" Type="String" />
<asp:Parameter Direction="Output" Name="ProductID" Type="Int32" />
</InsertParameters>
<DeleteParameters>
<asp:Parameter Name="original_ProductName" Type="String" />
<asp:Parameter Name="original_CategoryID" Type="Int32" />
<asp:Parameter Name="original_InStore" Type="Int16"/>
<asp:Parameter Name="original_Description" Type="String" />
<asp:Parameter Name="original_ProductID" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="CategoryID" Type="Int32" />
<asp:Parameter Name="Price" Type="decimal" />
<asp:Parameter Name="InStore" Type="Int16"/>
<asp:Parameter Name="Description" Type="String" />
<asp:Parameter Name="original_ProductName" Type="String" />
<asp:Parameter Name="original_CategoryID" Type="Int32" />
<asp:Parameter Name="original_InStore" Type="Int16"/>
<asp:Parameter Name="original_Description" Type="String" />
<asp:Parameter Name="original_ProductID" Type="Int32" />
</UpdateParameters>
</asp:SqlDataSource>
<br />
<asp:DetailsView AutoGenerateRows="False" DataKeyNames="ProductID" DataSourceID="SqlDataSource1"
DefaultMode="Insert" HeaderText="
插入新产品
" Height="50px" ID="DetailsView1"
runat="server" CellPadding="4" Font-Names="Verdana" Font-Size="XX-Small" ForeColor="#333333" GridLines="None" AutoGenerateInsertButton="True" >
<Fields>
<asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False"
ReadOnly="True" SortExpression="ProductID" />
<asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
<asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" />
<asp:BoundField DataField="Price" HeaderText="Price" SortExpression="Price" />
<asp:BoundField DataField="InStore" HeaderText="InStore" SortExpression="InStore" />
<asp:BoundField DataField="Description" HeaderText="Description" SortExpression="Description" />
</Fields>
… …
</asp:DetailsView>
</div>
</form>
</body>
</html>
代码
2-16 Conflict_ObjectDataSource.aspx
的部分代码
在这段代码里,
SqlDataSource
的
UpdateCommand
被设置为存储过程,该存储过程的名称为
UpdateProduct
,具体代码如
2-17
:
ALTER PROCEDURE
[UpdateProduct]
(
@ProductName varchar(50),
@CategoryID int,
@Price decimal,
@InStore int,
@Description nvarchar(100),
@original_ProductID int,
@original_ProductName varchar(50),
@original_CategoryID int,
@original_Price decimal,
@original_InStore int,
@original_Description nvarchar(100))
AS
update Products set ProductName=@ProductName,CategoryID=@CategoryID,
Price=@Price,InStore=@InStore,Description=@Description
WHERE (productID = @original_ProductID
AND ProductName=@original_ProductName
and CategoryID=@original_CategoryID
and price=@original_price
and InStore=@original_InStore
and description=@original_description)
代码
2-17 UpdateProduct
源代码
同样,
DeleteCommand
也被设置为存储过程
,
该存储过程的代码加
2-18
:
ALTER PROCEDURE [DeleteProduct]
(@original_ProductID int,
@original_ProductName varchar(50),
@original_CategoryID int,
@original_Price decimal,
@original_InStore int,
@original_Description nvarchar(100))
AS
DELETE FROM Products WHERE productID = @original_ProductID
AND ProductName=@original_ProductName
and CategoryID=@original_CategoryID
and price=@original_price
and InStore=@original_InStore
and description=@original_description
代码
2-18 DeleteProduct
源代码
SelectCommand
的存储过程为
GetAllProducts
,该语句较为简单如
2-19
ALTER PROCEDURE GetAllProducts
AS
select * from Products
代码
2-19 GetAllProducts
源代码
为了便于详细列举数据源控件的使用,我们使用了
DetailView
控件用于插入记录,
InsertCommand
被设置为存储过程,代码如
2-20.
ALTER PROCEDURE [InsertProduct]
(
@CategoryID int,
@Price decimal,
@InStore int,
@Description nvarchar(100),
@ProductID int output
)
AS
INSERT INTO products (ProductName,CategoryID,Price,InStore,Description) VALUES
(@ProductName,@CategoryID,@Price,@InStore,@Description)
SELECT ProductID = @@IDENTITY
代码
2-20 InsertProduct
源代码
若要运行此示例,请在单独的浏览器窗口中打开该示例的两个实例如图
2-36
。
图 2-36 准备编辑
然后对两个窗口中的相同行单击
“Edit”
(编辑)按钮,以便将该行置于编辑模式。在第一个窗口中,更改某个行值并单击
“Update”
(更新),并注意更新是成功的。在第二个窗口中,可以为相同行输入新值并单击
“Update”
(更新),但是更新不会成功,这是因为基础行值被第一个更新操作更改了。该示例检测到
Updated
或
Deleted
事件参数的
AffectedRows
属性为
0
,从而确认发生了冲突。
图
2-37
一个窗口对数据进行更新
另外,页面夏布有一个“输入产品”页面,可以利用该页面插入产品数据,如图
3-39
。在进行插入数据时,由于不会产生冲突,所以不用传递原
ProductID
等值。