在上面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一样。
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。
正如在前面的主题中所提到的,数据绑定控件在单独的 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等值。
点击此处下载本文源代码:/Files/mqingqing123/ObjectDataSource.rar