ASP.NET2.0 ObjectDataSource的使用详解(1)
ASP.NET2.0 ObjectDataSource的使用详解(2)
本系列文章将介绍ObjectDataSource的使用,为了内容的完成性,所以虽然简单,但是还是发到首页,不知道行不行
本系列文章主要参考MSDN,ASP.NET快速入门和ASP.NET的文章整理而成,将由浅入深说明ObjectDataSource的使用,仅供参考,不保证内容100%的争取
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等值。