检查与插入、更新和删除操作有关的事件 (C#)Examining the Events Associated with Inserting, Updating, and Deleting (C#)
作者: Scott Mitchellby Scott Mitchell
下载示例应用或下载 PDFDownload Sample App or Download PDF
在本教程中,我们将使用 ASP.NET 数据 Web 控件的插入、更新或删除操作前后的事件进行检查。In this tutorial we'll examine using the events that occur before, during, and after an insert, update, or delete operation of an ASP.NET data Web control. 我们还将了解如何自定义编辑界面,以便仅更新产品字段的子集。We'll also see how to customize the editing interface to only update a subset of the product fields.
简介Introduction
使用内置的插入、编辑或删除 GridView、DetailsView 或 FormView 控件的功能时,最终用户完成添加新记录或更新或删除现有记录的过程时,会发生各种步骤。When using the built-in inserting, editing, or deleting features of the GridView, DetailsView, or FormView controls, a variety of steps transpire when the end user completes the process of adding a new record or updating or deleting an existing record. 如前面的教程中所述,在 GridView 中编辑某一行时,"编辑" 按钮将被 "更新" 和 "取消" 按钮替换,并且 BoundFields 转换为文本框。As we discussed in the previous tutorial, when a row is edited in the GridView the Edit button is replaced by Update and Cancel buttons and the BoundFields turn into TextBoxes. 最终用户更新数据并单击 "更新" 后,将对回发执行以下步骤:After the end user updates the data and clicks Update, the following steps are performed on postback:
- GridView 使用已编辑记录的唯一标识字段(通过
DataKeyNames
属性)和用户输入的值填充其 ObjectDataSource 的UpdateParameters
The GridView populates its ObjectDataSource'sUpdateParameters
with the edited record's unique identifying field(s) (via theDataKeyNames
property) along with the values entered by the user - GridView 调用其 ObjectDataSource
Update()
方法,该方法反过来调用基础对象中的相应方法(ProductsDAL.UpdateProduct
,在前面的教程中)The GridView invokes its ObjectDataSource'sUpdate()
method, which in turn invokes the appropriate method in the underlying object (ProductsDAL.UpdateProduct
, in our previous tutorial) - 底层数据现在包含更新的更改,将重新绑定到 GridViewThe underlying data, which now includes the updated changes, is rebound to the GridView
在此步骤序列过程中,会触发大量事件,使我们能够创建事件处理程序,以便在需要时添加自定义逻辑。During this sequence of steps, a number of events fire, enabling us to create event handlers to add custom logic where needed. 例如,在步骤1之前,激发了 GridView 的 RowUpdating
事件。For example, prior to Step 1, the GridView's RowUpdating
event fires. 如果出现某些验证错误,我们可以在此时取消更新请求。We can, at this point, cancel the update request if there is some validation error. 调用 Update()
方法时,将激发 ObjectDataSource 的 Updating
事件,从而提供添加或自定义任意 UpdateParameters
的值的机会。When the Update()
method is invoked, the ObjectDataSource's Updating
event fires, providing an opportunity to add or customize the values of any of the UpdateParameters
. 在 ObjectDataSource 的基础对象的方法完成执行后,将引发 ObjectDataSource 的 Updated
事件。After the ObjectDataSource's underlying object's method has completed executing, the ObjectDataSource's Updated
event is raised. Updated
事件的事件处理程序可以检查有关更新操作的详细信息,例如,受影响的行数以及是否发生了异常。An event handler for the Updated
event can inspect the details about the update operation, such as how many rows were affected and whether or not an exception occurred. 最后,在步骤2之后,将激发 GridView 的 RowUpdated
事件;此事件的事件处理程序可以检查有关刚执行的更新操作的其他信息。Finally, after Step 2, the GridView's RowUpdated
event fires; an event handler for this event can examine additional information about the update operation just performed.
图1描述了更新 GridView 时的这一系列事件和步骤。Figure 1 depicts this series of events and steps when updating a GridView. 图1中的事件模式对于使用 GridView 进行更新并不是唯一的。The event pattern in Figure 1 is not unique to updating with a GridView. 从 GridView、DetailsView 或 FormView precipitates 插入、更新或删除数据时,对于数据 Web 控件和 ObjectDataSource,都具有相同的前期和后期级别事件的序列。Inserting, updating, or deleting data from the GridView, DetailsView, or FormView precipitates the same sequence of pre- and post-level events for both the data Web control and the ObjectDataSource.
图 1:更新 GridView 中的数据时,一系列事件和事件后激发(单击查看完全大小的图像)Figure 1: A Series of Pre- and Post-Events Fire When Updating Data in a GridView (Click to view full-size image)
在本教程中,我们将检查如何使用这些事件来扩展 ASP.NET 数据 Web 控件的内置插入、更新和删除功能。In this tutorial we'll examine using these events to extend the built-in inserting, updating, and deleting capabilities of the ASP.NET data Web controls. 我们还将了解如何自定义编辑界面,以便仅更新产品字段的子集。We'll also see how to customize the editing interface to only update a subset of the product fields.
步骤1:更新产品的ProductName
和UnitPrice
字段Step 1: Updating a Product'sProductName
andUnitPrice
Fields
在上一教程中,编辑界面中的所有不是只读的产品字段都必须包括在内。In the editing interfaces from the previous tutorial all product fields that were not read-only had to be included. 如果我们要从 GridView 中删除字段(例如 QuantityPerUnit
-更新数据时,数据 Web 控件不会将 ObjectDataSource 的 QuantityPerUnit
UpdateParameters
值。If we were to remove a field from the GridView - say QuantityPerUnit
- when updating the data the data Web control would not set the ObjectDataSource's QuantityPerUnit
UpdateParameters
value. 然后,ObjectDataSource 将 null
值传入 UpdateProduct
业务逻辑层(BLL)方法,该方法会将编辑的数据库记录的 QuantityPerUnit
列更改为 NULL
值。The ObjectDataSource would then pass in a null
value into the UpdateProduct
Business Logic Layer (BLL) method, which would change the edited database record's QuantityPerUnit
column to a NULL
value. 同样,如果从编辑界面删除了某个必填字段(如 ProductName
),则更新将失败,并出现 "列 ' ProductName ' 不允许空值" 异常。Similarly, if a required field, such as ProductName
, is removed from the editing interface, the update will fail with a "Column 'ProductName' does not allow nulls" exception. 此行为的原因是,ObjectDataSource 配置为调用 ProductsBLL
类的 UpdateProduct
方法,该方法需要每个产品字段的输入参数。The reason for this behavior was because the ObjectDataSource was configured to call the ProductsBLL
class's UpdateProduct
method, which expected an input parameter for each of the product fields. 因此,ObjectDataSource 的 UpdateParameters
集合包含该方法的每个输入参数的参数。Therefore, the ObjectDataSource's UpdateParameters
collection contained a parameter for each of the method's input parameters.
如果我们想要提供一个允许最终用户仅更新字段子集的数据 Web 控件,则需要以编程方式在 ObjectDataSource 的 Updating
事件处理程序中设置缺少的 UpdateParameters
值,或者创建和调用只需要一部分字段的 BLL 方法。If we want to provide a data Web control that allows the end user to only update a subset of fields, then we need to either programmatically set the missing UpdateParameters
values in the ObjectDataSource's Updating
event handler or create and call a BLL method that expects only a subset of the fields. 让我们探讨后一种方法。Let's explore this latter approach.
具体而言,我们将创建一个页面,只显示可编辑 GridView 中的 ProductName
和 UnitPrice
字段。Specifically, let's create a page that displays just the ProductName
and UnitPrice
fields in an editable GridView. 此 GridView 的编辑界面只允许用户更新两个显示的字段 ProductName
和 UnitPrice
。This GridView's editing interface will only allow the user to update the two displayed fields, ProductName
and UnitPrice
. 由于此编辑接口仅提供产品字段的子集,因此,我们需要创建一个 ObjectDataSource,该 ObjectDataSource 使用现有的 BLL UpdateProduct
方法,并在其 Updating
事件处理程序中以编程方式设置缺失的产品字段值,或者我们需要创建一个新的 BLL 方法,该方法只需要在 GridView 中定义的字段的子集。Since this editing interface only provides a subset of a product's fields, we either need to create an ObjectDataSource that uses the existing BLL's UpdateProduct
method and has the missing product field values set programmatically in its Updating
event handler, or we need to create a new BLL method that expects only the subset of fields defined in the GridView. 对于本教程,我们将使用后一个选项,并创建 UpdateProduct
方法的重载,该重载只使用三个输入参数: productName
、unitPrice
和 productID
:For this tutorial, let's use the latter option and create an overload of the UpdateProduct
method, one that takes in just three input parameters: productName
, unitPrice
, and productID
:
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
product.ProductName = productName;
if (unitPrice == null) product.SetUnitPriceNull();
else product.UnitPrice = unitPrice.Value;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
与原始 UpdateProduct
方法一样,此重载通过检查数据库中是否存在具有指定 ProductID
的产品开始。Like the original UpdateProduct
method, this overload starts by checking to see if there is a product in the database with the specified ProductID
. 否则,它将返回 false
,指示更新产品信息的请求失败。If not, it returns false
, indicating that the request to update the product information failed. 否则,它会相应地更新现有产品记录的 ProductName
和 UnitPrice
字段,并通过调用 TableAdapter 的 Update()
方法,并传入 ProductsRow
实例来提交更新。Otherwise it updates the existing product record's ProductName
and UnitPrice
fields accordingly and commits the update by calling the TableAdapter's Update()
method, passing in the ProductsRow
instance.
通过此类添加到 ProductsBLL
类,可以创建简化的 GridView 界面。With this addition to our ProductsBLL
class, we're ready to create the simplified GridView interface. 打开 EditInsertDelete
文件夹中的 DataModificationEvents.aspx
,并向页面添加 GridView。Open the DataModificationEvents.aspx
in the EditInsertDelete
folder and add a GridView to the page. 创建新的 ObjectDataSource,并将其配置为使用 ProductsBLL
类,并将其 Select()
方法映射到 GetProducts
,并将其 Update()
方法映射到仅采用 UpdateProduct
、productName
和 unitPrice
输入参数的 productID
重载。Create a new ObjectDataSource and configure it to use the ProductsBLL
class with its Select()
method mapping to GetProducts
and its Update()
method mapping to the UpdateProduct
overload that takes in only the productName
, unitPrice
, and productID
input parameters. 图2显示了在将 ObjectDataSource 的 Update()
方法映射到 ProductsBLL
类的新 UpdateProduct
方法重载时创建数据源向导。Figure 2 shows the Create Data Source wizard when mapping the ObjectDataSource's Update()
method to the ProductsBLL
class's new UpdateProduct
method overload.
图 2:将 ObjectDataSource 的 Update()
方法映射到新的 UpdateProduct
重载(单击以查看完全大小的映像)Figure 2: Map the ObjectDataSource's Update()
Method to the New UpdateProduct
Overload (Click to view full-size image)
由于我们的示例最初只需要能够编辑数据,而不是插入或删除记录,因此请花一些时间来明确指出,不应通过转到 "插入" 和 "删除" 选项卡,然后从下拉列表中选择 "(无)" 来将 ObjectDataSource 的 Insert()
和 Delete()
方法映射到任何 ProductsBLL
类的方法。Since our example will initially just need the ability to edit data, but not to insert or delete records, take a moment to explicitly indicate that the ObjectDataSource's Insert()
and Delete()
methods shouldn't be mapped to any of the ProductsBLL
class's methods by going to the INSERT and DELETE tabs and choosing (None) from the drop-down list.
从 "插入" 和 "删除" 选项卡的下拉列表中选择 "(无)"
图 3:从 "插入" 和 "删除" 选项卡的下拉列表中选择 "(无)" (单击查看完全大小的图像)Figure 3: Choose (None) From the Drop-Down List for the INSERT and DELETE Tabs (Click to view full-size image)
完成此向导后,请从 GridView 的智能标记中选中 "启用编辑" 复选框。After completing this wizard check the Enable Editing checkbox from the GridView's smart tag.
完成创建数据源向导并将其绑定到 GridView 后,Visual Studio 将为这两个控件创建了声明性语法。With the completion of the Create Data Source wizard and binding that to the GridView, Visual Studio has created the declarative syntax for both controls. 请中转到 "源" 视图以检查 ObjectDataSource 的声明性标记,如下所示:Go to the Source view to inspect the ObjectDataSource's declarative markup, which is shown below:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
TypeName="ProductsBLL" UpdateMethod="UpdateProduct">
<UpdateParameters>
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="unitPrice" Type="Decimal" />
<asp:Parameter Name="productID" Type="Int32" />
</UpdateParameters>
</asp:ObjectDataSource>
由于不存在用于 ObjectDataSource 的 Insert()
和 Delete()
方法的映射,因此没有 InsertParameters
或 DeleteParameters
部分。Since there are no mappings for the ObjectDataSource's Insert()
and Delete()
methods, there are no InsertParameters
or DeleteParameters
sections. 此外,由于 Update()
方法映射到仅接受三个输入参数的 UpdateProduct
方法重载,UpdateParameters
节只包含三个 Parameter
实例。Furthermore, since the Update()
method is mapped to the UpdateProduct
method overload that only accepts three input parameters, the UpdateParameters
section has just three Parameter
instances.
请注意,ObjectDataSource 的 OldValuesParameterFormatString
属性设置为 original_{0}
。Note that the ObjectDataSource's OldValuesParameterFormatString
property is set to original_{0}
. 使用 "配置数据源" 向导时,Visual Studio 会自动设置此属性。This property is set automatically by Visual Studio when using the Configure Data Source wizard. 但是,由于 BLL 方法不希望传入原始 ProductID
值,因此请从 ObjectDataSource 的声明性语法中删除此属性赋值。However, since our BLL methods don't expect the original ProductID
value to be passed in, remove this property assignment altogether from the ObjectDataSource's declarative syntax.
Note
如果只是从设计视图的属性窗口中清除 OldValuesParameterFormatString
属性值,则该属性仍将在声明性语法中存在,但会设置为空字符串。If you simply clear out the OldValuesParameterFormatString
property value from the Properties window in the Design view, the property will still exist in the declarative syntax, but will be set to an empty string. 从声明性语法中删除完全相同的属性,或者从属性窗口中将此值设置为默认值,{0}
。Either remove the property altogether from the declarative syntax or, from the Properties window, set the value to the default, {0}
.
尽管 ObjectDataSource 只为产品的名称、价格和 ID UpdateParameters
,但 Visual Studio 已为产品的每个字段在 GridView 中添加了 BoundField 或 CheckBoxField。While the ObjectDataSource only has UpdateParameters
for the product's name, price, and ID, Visual Studio has added a BoundField or CheckBoxField in the GridView for each of the product's fields.
图 4: GridView 包含产品每个字段的 BoundField 或 CheckBoxField (单击以查看完全大小的图像)Figure 4: The GridView Contains a BoundField or CheckBoxField for Each of the Product's Fields (Click to view full-size image)
当最终用户编辑产品并单击其 "更新" 按钮时,GridView 会枚举不是只读字段的字段。When the end user edits a product and clicks its Update button, the GridView enumerates those fields that were not read-only. 然后,它将 ObjectDataSource 的 UpdateParameters
集合中相应参数的值设置为用户输入的值。It then sets the value of the corresponding parameter in the ObjectDataSource's UpdateParameters
collection to the value entered by the user. 如果没有相应的参数,则 GridView 会将其添加到集合中。If there is not a corresponding parameter, the GridView adds one to the collection. 因此,如果 GridView 包含所有产品的字段的 BoundFields 和 CheckBoxFields,则 ObjectDataSource 最终将调用获取所有这些参数的 UpdateProduct
重载,尽管 ObjectDataSource 的声明性标记只指定了三个输入参数(参见图5)。Therefore, if our GridView contains BoundFields and CheckBoxFields for all of the product's fields, the ObjectDataSource will end up invoking the UpdateProduct
overload that takes in all of these parameters, despite the fact that the ObjectDataSource's declarative markup specifies only three input parameters (see Figure 5). 同样,如果 GridView 中存在与 UpdateProduct
重载的输入参数不对应的非只读 product 字段的组合,则在尝试更新时将引发异常。Similarly, if there is some combination of non-read-only product fields in the GridView that doesn't correspond to the input parameters for a UpdateProduct
overload, an exception will be raised when attempting to update.
图 5: GridView 将参数添加到 ObjectDataSource 的 UpdateParameters
集合(单击查看完全大小的映像)Figure 5: The GridView Will Add Parameters to the ObjectDataSource's UpdateParameters
Collection (Click to view full-size image)
若要确保 ObjectDataSource 调用只采用产品名称、价格和 ID 的 UpdateProduct
重载,则需要将 GridView 限制为仅具有 ProductName
和 UnitPrice
的可编辑字段。To ensure that the ObjectDataSource invokes the UpdateProduct
overload that takes in just the product's name, price, and ID, we need to restrict the GridView to having editable fields for just the ProductName
and UnitPrice
. 这可以通过以下方式实现:删除其他 BoundFields 和 CheckBoxFields,将其他字段的 ReadOnly
属性设置为 true
或这两者的组合。This can be accomplished by removing the other BoundFields and CheckBoxFields, by setting those other fields' ReadOnly
property to true
, or by some combination of the two. 对于本教程,我们只需删除除 ProductName
和 UnitPrice
BoundFields 以外的所有 GridView 字段,之后 GridView 的声明性标记将如下所示:For this tutorial let's simply remove all GridView fields except the ProductName
and UnitPrice
BoundFields, after which the GridView's declarative markup will look like:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ObjectDataSource1">
<Columns>
<asp:CommandField ShowEditButton="True" />
<asp:BoundField DataField="ProductName"
HeaderText="ProductName" SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice"
SortExpression="UnitPrice" />
</Columns>
</asp:GridView>
即使 UpdateProduct
重载需要三个输入参数,在 GridView 中我们还是只有两 BoundFields。Even though the UpdateProduct
overload expects three input parameters, we only have two BoundFields in our GridView. 这是因为 productID
输入参数是一个主键值,并且通过所编辑的行的 DataKeyNames
属性的值进行传递。This is because the productID
input parameter is a primary key value and passed in through the value of the DataKeyNames
property for the edited row.
我们的 GridView 与 UpdateProduct
重载一起使用,允许用户仅编辑产品的名称和价格,而不会丢失任何其他产品字段。Our GridView, along with the UpdateProduct
overload, allows a user to edit just the name and price of a product without losing any of the other product fields.
图 6:接口只允许编辑产品的名称和价格(单击查看全尺寸图像)Figure 6: The Interface Allows Editing Just the Product's Name and Price (Click to view full-size image)
Note
如前面的教程中所述,必须启用 GridView 的视图状态(默认行为),这一点至关重要。As discussed in the previous tutorial, it is vitally important that the GridView s view state be enabled (the default behavior). 如果将 GridView EnableViewState
属性设置为 "false
",则可能会遇到使并发用户无意中删除或编辑记录的风险。If you set the GridView s EnableViewState
property to false
, you run the risk of having concurrent users unintentionally deleting or editing records. 有关详细信息,请参阅ASP.NET 2.0 GridViews/DetailsView/FormViews 的并发问题,其中支持编辑和/或删除并禁用了其视图状态。See WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled for more information.
改善UnitPrice
格式Improving theUnitPrice
Formatting
虽然图6中所示的 GridView 示例是可行的,但 "UnitPrice
" 字段不是格式的,这会导致价格显示缺少任何货币符号,且具有四个小数位。While the GridView example shown in Figure 6 works, the UnitPrice
field is not formatted at all, resulting in a price display that lacks any currency symbols and has four decimal places. 若要为不可编辑的行应用货币格式设置,只需将 UnitPrice
BoundField 的 DataFormatString
属性设置为 {0:c}
,并将其 HtmlEncode
属性设置为 false
。To apply a currency formatting for the non-editable rows, simply set the UnitPrice
BoundField's DataFormatString
property to {0:c}
and its HtmlEncode
property to false
.
图 7:相应地设置 UnitPrice
的 DataFormatString
和 HtmlEncode
属性(单击以查看完全大小的图像)Figure 7: Set the UnitPrice
's DataFormatString
and HtmlEncode
Properties Accordingly (Click to view full-size image)
进行此更改后,不可编辑的行将价格设置为货币格式;但已编辑的行仍会显示值,但不包含货币符号和四个小数位。With this change, the non-editable rows format the price as a currency; the edited row, however, still displays the value without the currency symbol and with four decimal places.
图 8:不可编辑的行现在已格式化为货币值(单击以查看完全大小的图像)Figure 8: Non-Editable Rows are Now Formatted as Currency Values (Click to view full-size image)
通过将 BoundField 的 ApplyFormatInEditMode
属性设置为 true
(默认值为 false
),可以将 DataFormatString
属性中指定的格式设置指令应用于编辑界面。The formatting instructions specified in the DataFormatString
property can be applied to the editing interface by setting the BoundField's ApplyFormatInEditMode
property to true
(the default is false
). 请花点时间将此属性设置为 true
。Take a moment to set this property to true
.
图 9:将 UnitPrice
BoundField 的 ApplyFormatInEditMode
属性设置为 true
(单击查看完全大小的图像)Figure 9: Set the UnitPrice
BoundField's ApplyFormatInEditMode
property to true
(Click to view full-size image)
进行此更改后,所编辑的行中显示的 UnitPrice
的值也将设置为货币格式。With this change, the value of the UnitPrice
displayed in the edited row is also formatted as a currency.
图 10:已编辑的行的 UnitPrice
值现在已设置为货币格式(单击查看完全尺寸的图像)Figure 10: The Edited Row's UnitPrice
Value is Now Formatted as a Currency (Click to view full-size image)
但是,在文本框(如 $19.00)中使用货币符号更新产品会引发 FormatException
。However, updating a product with the currency symbol in the textbox such as $19.00 throws a FormatException
. 当 GridView 尝试将用户提供的值分配给 ObjectDataSource 的 UpdateParameters
集合时,它无法将 UnitPrice
字符串 "$19.00" 转换为参数所需的 decimal
(参见图11)。When the GridView attempts to assign the user-supplied values to the ObjectDataSource's UpdateParameters
collection it is unable to convert the UnitPrice
string "$19.00" into the decimal
required by the parameter (see Figure 11). 若要解决此情况,可以为 GridView 的 RowUpdating
事件创建事件处理程序,并让它将用户提供的 UnitPrice
分析为货币格式的 decimal
。To remedy this we can create an event handler for the GridView's RowUpdating
event and have it parse the user-supplied UnitPrice
as a currency-formatted decimal
.
GridView 的 RowUpdating
事件接受GridViewUpdateEventArgs类型的对象作为其第二个参数,其中包含一个 NewValues
字典作为其属性之一,其中包含可分配给 ObjectDataSource 的 UpdateParameters
集合的用户提供的值。The GridView's RowUpdating
event accepts as its second parameter an object of type GridViewUpdateEventArgs, which includes a NewValues
dictionary as one of its properties that holds the user-supplied values ready to be assigned to the ObjectDataSource's UpdateParameters
collection. 我们可以使用以下代码行在 RowUpdating
事件处理程序中,用货币格式分析的十进制值覆盖 NewValues
集合中的现有 UnitPrice
值:We can overwrite the existing UnitPrice
value in the NewValues
collection with a decimal value parsed using the currency format with the following lines of code in the RowUpdating
event handler:
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
if (e.NewValues["UnitPrice"] != null)
e.NewValues["UnitPrice"] =
decimal.Parse(e.NewValues["UnitPrice"].ToString(),
System.Globalization.NumberStyles.Currency);
}
如果用户提供了一个 UnitPrice
值(如 "$19.00"),则使用 Decimal 计算所得的十进制值覆盖此值。 Parse,将值分析为货币。If the user has supplied a UnitPrice
value (such as "$19.00"), this value is overwritten with the decimal value computed by Decimal.Parse, parsing the value as a currency. 这将在任何货币符号、逗号、小数点等的情况下正确分析 decimal,并使用 NumberStyles命名空间中的枚举。This will correctly parse the decimal in the event of any currency symbols, commas, decimal points, and so on, and uses the NumberStyles enumeration in the System.Globalization namespace.
图11显示了用户提供的 UnitPrice
中的货币符号导致的问题,以及如何利用 GridView 的 RowUpdating
事件处理程序来正确分析此类输入。Figure 11 shows both the problem caused by currency symbols in the user-supplied UnitPrice
, along with how the GridView's RowUpdating
event handler can be utilized to correctly parse such input.
图 11:已编辑的行的 UnitPrice
值现在已设置为货币格式(单击查看完全尺寸的图像)Figure 11: The Edited Row's UnitPrice
Value is Now Formatted as a Currency (Click to view full-size image)
步骤2:禁止NULL UnitPrices
Step 2: ProhibitingNULL UnitPrices
当数据库被配置为允许 Products
表的 UnitPrice
列中 NULL
值时,我们可能希望阻止用户通过指定 NULL
UnitPrice
值来访问此特定页面。While the database is configured to allow NULL
values in the Products
table's UnitPrice
column, we may want to prevent users visiting this particular page from specifying a NULL
UnitPrice
value. 也就是说,如果用户在编辑产品行时无法输入 UnitPrice
值,而不是将结果保存到数据库中,我们想要显示一条消息,告知用户在此页上,任何已编辑的产品都必须指定价格。That is, if a user fails to enter a UnitPrice
value when editing a product row, rather than save the results to the database we want to display a message informing the user that, through this page, any edited products must have a price specified.
传递到 GridView RowUpdating
事件处理程序中的 GridViewUpdateEventArgs
对象包含一个 Cancel
属性,如果设置为 true
,则终止更新过程。The GridViewUpdateEventArgs
object passed into the GridView's RowUpdating
event handler contains a Cancel
property that, if set to true
, terminates the updating process. 接下来,我们将扩展 RowUpdating
事件处理程序,将 e.Cancel
设置为 true
并显示一条消息,说明 NewValues
集合中的 UnitPrice
值是否 null
。Let's extend the RowUpdating
event handler to set e.Cancel
to true
and display a message explaining why if the UnitPrice
value in the NewValues
collection is null
.
首先,将 "标签" Web 控件添加到名为 "MustProvideUnitPriceMessage
" 的页。Start by adding a Label Web control to the page named MustProvideUnitPriceMessage
. 如果在更新产品时用户未能指定 UnitPrice
值,则将显示此 "标签" 控件。This Label control will be displayed if the user fails to specify a UnitPrice
value when updating a product. 将标签的 Text
属性设置为 "必须提供产品价格"。Set the Label's Text
property to "You must provide a price for the product." 我还使用以下定义,在名为 Warning
的 Styles.css
中创建了一个新的 CSS 类:I've also created a new CSS class in Styles.css
named Warning
with the following definition:
.Warning
{
color: Red;
font-style: italic;
font-weight: bold;
font-size: x-large;
}
最后,将标签的 CssClass
属性设置为 Warning
。Finally, set the Label's CssClass
property to Warning
. 此时,设计器应显示警告消息,其中红色、粗体、斜体、小字体大小在 GridView 之上,如图12所示。At this point the Designer should show the warning message in a red, bold, italic, extra large font size above the GridView, as shown in Figure 12.
图 12:已在 GridView 上方添加标签(单击以查看完全尺寸的图像)Figure 12: A Label Has Been Added Above the GridView (Click to view full-size image)
默认情况下,应隐藏此标签,以便将其 Visible
属性设置为在 Page_Load
事件处理程序中 false
:By default, this Label should be hidden, so set its Visible
property to false
in the Page_Load
event handler:
protected void Page_Load(object sender, EventArgs e)
{
MustProvideUnitPriceMessage.Visible = false;
}
如果用户在未指定 UnitPrice
的情况下尝试更新产品,我们想要取消更新并显示警告标签。If the user attempts to update a product without specifying the UnitPrice
, we want to cancel the update and display the warning label. 增加 GridView 的 RowUpdating
事件处理程序,如下所示:Augment the GridView's RowUpdating
event handler as follows:
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
if (e.NewValues["UnitPrice"] != null)
{
e.NewValues["UnitPrice"] =
decimal.Parse(e.NewValues["UnitPrice"].ToString(),
System.Globalization.NumberStyles.Currency);
}
else
{
// Show the Label
MustProvideUnitPriceMessage.Visible = true;
// Cancel the update
e.Cancel = true;
}
}
如果用户在未指定价格的情况下尝试保存产品,则将取消更新并显示有用的消息。If a user attempts to save a product without specifying a price, the update is cancelled and a helpful message is displayed. 尽管数据库(和业务逻辑)允许 NULL
UnitPrice
,但这一特定的 ASP.NET 页面并不允许。While the database (and business logic) allows for NULL
UnitPrice
s, this particular ASP.NET page does not.
图 13:用户不能将 UnitPrice
留空(单击查看完全尺寸的图像)Figure 13: A User Cannot Leave UnitPrice
Blank (Click to view full-size image)
到目前为止,我们已了解如何使用 GridView 的 RowUpdating
事件以编程方式更改分配给 ObjectDataSource 的 UpdateParameters
集合的参数值,以及如何完全取消更新过程。So far we have seen how to use the GridView's RowUpdating
event to programmatically alter the parameter values assigned to the ObjectDataSource's UpdateParameters
collection as well as how to cancel the updating process altogether. 这些概念将传输到 DetailsView 和 FormView 控件,并适用于插入和删除。These concepts carry over to the DetailsView and FormView controls and also apply to inserting and deleting.
还可以通过事件处理程序在 ObjectDataSource 级别为其 Inserting
、Updating
和 Deleting
事件完成这些任务。These tasks can also be done at the ObjectDataSource level through event handlers for its Inserting
, Updating
, and Deleting
events. 这些事件在调用基础对象的关联方法之前激发,并为最后修改输入参数集合或完全取消操作提供了最后机会。These events fire before the associated method of the underlying object is invoked and provide a last-chance opportunity to modify the input parameters collection or cancel the operation outright. 这三个事件的事件处理程序将传递ObjectDataSourceMethodEventArgs类型的对象,该对象具有两个相关属性:The event handlers for these three events are passed an object of type ObjectDataSourceMethodEventArgs that has two properties of interest:
- 取消,此操作如果设置为
true
,则取消正在执行的操作Cancel, which, if set totrue
, cancels the operation being performed - 输入参数,它是
InsertParameters
、UpdateParameters
或DeleteParameters
的集合,具体取决于事件处理程序是用于Inserting
、Updating
还是Deleting
事件InputParameters, which is the collection ofInsertParameters
,UpdateParameters
, orDeleteParameters
, depending on whether the event handler is for theInserting
,Updating
, orDeleting
event
为了说明如何在 ObjectDataSource 级别使用参数值,让我们在页面中包含一项 DetailsView,使用户能够添加新产品。To illustrate working with the parameter values at the ObjectDataSource level, let's include a DetailsView in our page that allows the users to add a new product. 此 DetailsView 将用于提供一个界面,用于快速将新产品添加到数据库。This DetailsView will be used to provide an interface for quickly adding a new product to the database. 若要在添加新产品时保持一致的用户界面,则允许用户仅输入 "ProductName
" 和 "UnitPrice
" 字段的值。To keep a consistent user interface when adding a new product let's allow the user to only enter values for the ProductName
and UnitPrice
fields. 默认情况下,DetailsView 的插入界面中未提供的那些值将设置为 NULL
数据库值。By default, those values that aren't supplied in the DetailsView's inserting interface will be set to a NULL
database value. 但是,我们可以使用 ObjectDataSource 的 Inserting
事件注入不同的默认值,正如我们稍后将看到的那样。However, we can use the ObjectDataSource's Inserting
event to inject different default values, as we'll see shortly.
步骤3:提供添加新产品的接口Step 3: Providing an Interface to Add New Products
将 DetailsView 从工具箱拖到 GridView 上方的设计器中,清除其 Height
和 Width
属性,并将其绑定到页面上已存在的 ObjectDataSource。Drag a DetailsView from the Toolbox onto the Designer above the GridView, clear out its Height
and Width
properties, and bind it to the ObjectDataSource already present on the page. 这会为产品的每个字段添加 BoundField 或 CheckBoxField。This will add a BoundField or CheckBoxField for each of the product's fields. 由于我们希望使用此 DetailsView 来添加新产品,因此需要从智能标记中选中 "启用插入" 选项;但是,没有这样的选项,因为 ObjectDataSource 的 Insert()
方法未映射到 ProductsBLL
类中的方法(请记住,在配置数据源时将此映射设置为(None),请参见图3)。Since we want to use this DetailsView to add new products, we need to check the Enable Inserting option from the smart tag; however, there's no such option because the ObjectDataSource's Insert()
method is not mapped to a method in the ProductsBLL
class (recall that we set this mapping to (None) when configuring the data source see Figure 3).
若要配置 ObjectDataSource,请从其智能标记中选择 "配置数据源" 链接,启动向导。To configure the ObjectDataSource, select the Configure Data Source link from its smart tag, launching the wizard. 第一个屏幕允许您更改 ObjectDataSource 绑定到的基础对象;将其设置为 ProductsBLL
。The first screen allows you to change the underlying object the ObjectDataSource is bound to; leave it set to ProductsBLL
. 下一屏幕中列出了从 ObjectDataSource 的方法到基础对象的映射。The next screen lists the mappings from the ObjectDataSource's methods to the underlying object's. 即使我们明确指出,不应将 Insert()
和 Delete()
方法映射到任何方法,如果您访问 "插入" 和 "删除" 选项卡,您将看到映射存在。Even though we explicitly indicated that the Insert()
and Delete()
methods should not be mapped to any methods, if you go to the INSERT and DELETE tabs you'll see that a mapping is there. 这是因为 ProductsBLL
的 AddProduct
和 DeleteProduct
方法使用 DataObjectMethodAttribute
特性来指示它们分别是 Insert()
和 Delete()
的默认方法。This is because the ProductsBLL
's AddProduct
and DeleteProduct
methods use the DataObjectMethodAttribute
attribute to indicate that they are the default methods for Insert()
and Delete()
, respectively. 因此,在每次运行向导时,ObjectDataSource 向导会选择这些值,除非显式指定了某些其他值。Hence, the ObjectDataSource wizard selects these each time you run the wizard unless there's some other value explicitly specified.
保留指向 AddProduct
方法的 Insert()
方法,但再次将 "删除" 选项卡的下拉列表设置为 "(无)"。Leave the Insert()
method pointing to the AddProduct
method, but again set the DELETE tab's drop-down list to (None).
图 14:将 "插入" 选项卡的下拉列表设置为 AddProduct
方法(单击以查看完全大小的图像)Figure 14: Set the INSERT Tab's Drop-Down List to the AddProduct
Method (Click to view full-size image)
图 15:将 "删除" 选项卡的下拉列表设置为 "(无)" (单击查看完全大小的图像)Figure 15: Set the DELETE Tab's Drop-Down List to (None) (Click to view full-size image)
进行这些更改后,ObjectDataSource 的声明性语法将展开以包括 InsertParameters
集合,如下所示:After making these changes, the ObjectDataSource's declarative syntax will be expanded to include an InsertParameters
collection, as shown below:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
SelectMethod="GetProducts" TypeName="ProductsBLL"
UpdateMethod="UpdateProduct" OnUpdating="ObjectDataSource1_Updating"
InsertMethod="AddProduct" OldValuesParameterFormatString="original_{0}">
<UpdateParameters>
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="unitPrice" Type="Decimal" />
<asp:Parameter Name="productID" Type="Int32" />
</UpdateParameters>
<InsertParameters>
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="supplierID" Type="Int32" />
<asp:Parameter Name="categoryID" Type="Int32" />
<asp:Parameter Name="quantityPerUnit" Type="String" />
<asp:Parameter Name="unitPrice" Type="Decimal" />
<asp:Parameter Name="unitsInStock" Type="Int16" />
<asp:Parameter Name="unitsOnOrder" Type="Int16" />
<asp:Parameter Name="reorderLevel" Type="Int16" />
<asp:Parameter Name="discontinued" Type="Boolean" />
</InsertParameters>
</asp:ObjectDataSource>
重新运行已添加回 OldValuesParameterFormatString
属性的向导。Rerunning the wizard added back the OldValuesParameterFormatString
property. 通过将此属性设置为默认值({0}
)或从声明性语法中删除它,花一些时间。Take a moment to clear this property by setting it to the default value ({0}
) or removing it altogether from the declarative syntax.
借助提供插入功能的 ObjectDataSource,DetailsView 的智能标记现在将包括 "启用插入" 复选框;返回到设计器并选中此选项。With the ObjectDataSource providing inserting capabilities, the DetailsView's smart tag will now include the Enable Inserting checkbox; return to the Designer and check this option. 接下来,削减 DetailsView,使其只具有两个 BoundFields ProductName
UnitPrice
和 CommandField。Next, pare down the DetailsView so that it only has two BoundFields - ProductName
and UnitPrice
- and the CommandField. 此时,DetailsView 的声明性语法应该如下所示:At this point the DetailsView's declarative syntax should look like:
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False"
DataKeyNames="ProductID" DataSourceID="ObjectDataSource1">
<Fields>
<asp:BoundField DataField="ProductName"
HeaderText="ProductName" SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice"
SortExpression="UnitPrice" />
<asp:CommandField ShowInsertButton="True" />
</Fields>
</asp:DetailsView>
图16此时会显示此页。Figure 16 shows this page when viewed through a browser at this point. 如您所见,DetailsView 列出了第一个产品的名称和价格(Chai)。As you can see, the DetailsView lists the name and price of the first product (Chai). 但我们想要的是一个插入接口,该接口为用户提供了一种将新产品快速添加到数据库的方法。What we want, however, is an inserting interface that provides a means for the user to quickly add a new product to the database.
图 16: DetailsView 当前呈现为只读模式(单击以查看完全大小的图像)Figure 16: The DetailsView is Currently Rendered in Read-Only Mode (Click to view full-size image)
若要在插入模式下显示 DetailsView,需要将 DefaultMode
属性设置为 Inserting
。In order to show the DetailsView in its inserting mode we need to set the DefaultMode
property to Inserting
. 这会在首次访问时在插入模式下呈现 DetailsView,并在插入新记录后将其保存。This renders the DetailsView in insert mode when first visited and keeps it there after inserting a new record. 如图17所示,此类 DetailsView 提供了一个用于添加新记录的快速界面。As Figure 17 shows, such a DetailsView provides a quick interface for adding a new record.
图 17: DetailsView 提供了一个界面,用于快速添加新产品(单击以查看完全大小的映像)Figure 17: The DetailsView Provides an Interface for Quickly Adding a New Product (Click to view full-size image)
当用户输入产品名称和价格(例如,如图17所示的 "Acme 水源" 和1.99)并单击 "插入" 时,将在添加到数据库中的新产品记录中 culminating 回发可以和插入工作流开始。When the user enters a product name and price (such as "Acme Water" and 1.99, as in Figure 17) and clicks Insert, a postback ensues and the inserting workflow commences, culminating in a new product record being added to the database. DetailsView 维护其插入界面,GridView 会自动重新绑定到其数据源,以便包括新产品,如图18所示。The DetailsView maintains its inserting interface and the GridView is automatically rebound to its data source in order to include the new product, as shown in Figure 18.
图 18:已将产品 "Acme 水源" 添加到数据库中Figure 18: The Product "Acme Water" Has Been Added to the Database
虽然图18中的 GridView 并未显示,但从 DetailsView 界面 CategoryID
、SupplierID
、QuantityPerUnit
等的产品字段被分配 NULL
数据库值。While the GridView in Figure 18 doesn't show it, the product fields lacking from the DetailsView interface CategoryID
, SupplierID
, QuantityPerUnit
, and so on are assigned NULL
database values. 可以通过执行以下步骤来查看此内容:You can see this by performing the following steps:
- 在 Visual Studio 中转到服务器资源管理器Go to the Server Explorer in Visual Studio
- 展开
NORTHWND.MDF
数据库节点Expanding theNORTHWND.MDF
database node - 右键单击 "
Products
数据库" 表节点Right-click on theProducts
database table node - 选择 "显示表数据"Select Show Table Data
这将列出 Products
表中的所有记录。This will list all of the records in the Products
table. 如图19所示,除 ProductID
、ProductName
和 UnitPrice
之外的所有新产品列都具有 NULL
值。As Figure 19 shows, all of our new product's columns other than ProductID
, ProductName
, and UnitPrice
have NULL
values.
图 19: DetailsView 中未提供的产品字段分配 NULL
值(单击以查看完全大小的图像)Figure 19: The Product Fields Not Provided in the DetailsView are Assigned NULL
Values (Click to view full-size image)
对于一个或多个此类值,我们可能需要提供除 NULL
以外的默认值,因为 NULL
不是最佳的默认选项,或者数据库列本身不允许 NULL
。We may want to provide a default value other than NULL
for one or more of these column values, either because NULL
isn't the best default option or because the database column itself doesn't allow NULL
s. 为实现此目的,我们可以通过编程方式设置 DetailsView 的 InputParameters
集合的参数值。To accomplish this we can programmatically set the values of the parameters of the DetailsView's InputParameters
collection. 可在 DetailsView 的 ItemInserting
事件的事件处理程序或 ObjectDataSource 的 Inserting
事件的事件处理程序中完成此分配。This assignment can be done either in the event handler for the DetailsView's ItemInserting
event or the ObjectDataSource's Inserting
event. 由于我们已经了解了如何在数据 Web 控件级别使用前期事件和后级事件,因此让我们来探索这次使用 ObjectDataSource 的事件。Since we've already looked at using the pre- and post-level events at the data Web control level, let's explore using the ObjectDataSource's events this time.
步骤4:为CategoryID
和SupplierID
参数赋值Step 4: Assigning Values to theCategoryID
andSupplierID
Parameters
在本教程中,我们假设你在通过此接口添加新产品时,为应用程序分配了一个 CategoryID
,并 SupplierID
值为1。For this tutorial let's imagine that for our application when adding a new product through this interface it should be assigned a CategoryID
and SupplierID
value of 1. 如前文所述,ObjectDataSource 包含一对在数据修改过程中激发的前期和后级事件。As mentioned earlier, the ObjectDataSource has a pair of pre- and post-level events that fire during the data modification process. 调用 Insert()
方法时,ObjectDataSource 首先引发其 Inserting
事件,然后调用其 Insert()
方法已映射到的方法,并最终引发 Inserted
事件。When its Insert()
method is invoked, the ObjectDataSource first raises its Inserting
event, then calls the method that its Insert()
method has been mapped to, and finally raises the Inserted
event. Inserting
事件处理程序为我们的最后一次机会来调整输入参数或取消操作。The Inserting
event handler affords us one last opportunity to tweak the input parameters or cancel the operation outright.
Note
在实际应用程序中,你可能希望让用户指定类别和供应商,或者根据某些条件或业务逻辑为其选择此值(而不是盲目选择 ID 1)。In a real-world application you would likely want to either let the user specify the category and supplier or would pick this value for them based on some criteria or business logic (rather than blindly selecting an ID of 1). 无论如何,此示例演示如何以编程方式设置 ObjectDataSource 的前期事件的输入参数的值。Regardless, the example illustrates how to programmatically set the value of an input parameter from the ObjectDataSource's pre-level event.
请花片刻时间为 ObjectDataSource 的 Inserting
事件创建事件处理程序。Take a moment to create an event handler for the ObjectDataSource's Inserting
event. 请注意,事件处理程序的第二个输入参数是类型 ObjectDataSourceMethodEventArgs
的对象,它具有用于访问参数集合(InputParameters
)的属性和用于取消操作的属性(Cancel
)。Notice that the event handler's second input parameter is an object of type ObjectDataSourceMethodEventArgs
, which has a property to access the parameters collection (InputParameters
) and a property to cancel the operation (Cancel
).
protected void ObjectDataSource1_Inserting
(object sender, ObjectDataSourceMethodEventArgs e)
{
}
此时,InputParameters
属性包含具有从 DetailsView 分配的值的 ObjectDataSource 的 InsertParameters
集合。At this point, the InputParameters
property contains the ObjectDataSource's InsertParameters
collection with the values assigned from the DetailsView. 若要更改其中一个参数的值,只需使用: e.InputParameters["paramName"] = value
。To change the value of one of these parameters, simply use: e.InputParameters["paramName"] = value
. 因此,若要将 CategoryID
和 SupplierID
设置为值1,请调整 Inserting
事件处理程序,使其类似于以下内容:Therefore, to set the CategoryID
and SupplierID
to values of 1, adjust the Inserting
event handler to look like the following:
protected void ObjectDataSource1_Inserting
(object sender, ObjectDataSourceMethodEventArgs e)
{
e.InputParameters["CategoryID"] = 1;
e.InputParameters["SupplierID"] = 1;
}
这次添加新产品(如 Acme Soda)时,新产品的 CategoryID
和 SupplierID
列都设置为1(请参阅图20)。This time when adding a new product (such as Acme Soda), the CategoryID
and SupplierID
columns of the new product are set to 1 (see Figure 20).
图 20:新产品现在的 CategoryID
和 SupplierID
值设置为1(单击查看完全大小的图像)Figure 20: New Products Now Have Their CategoryID
and SupplierID
Values Set to 1 (Click to view full-size image)
摘要Summary
在编辑、插入和删除进程的过程中,数据 Web 控件和 ObjectDataSource 都继续经历多个前期和后期级别事件。During the editing, inserting, and deleting process, both the data Web control and the ObjectDataSource proceed through a number of pre- and post-level events. 在本教程中,我们检查了预级别事件,并了解了如何使用这些事件来自定义输入参数或从数据 Web 控件和 ObjectDataSource 的事件中取消数据修改操作。In this tutorial we examined the pre-level events and saw how to use these to customize the input parameters or cancel the data modification operation altogether both from the data Web control and ObjectDataSource's events. 在下一教程中,我们将介绍如何创建和使用事件处理程序来执行后级事件。In the next tutorial we'll look at creating and using event handlers for the post-level events.
很高兴编程!Happy Programming!
关于作者About the Author
Scott Mitchell,创始人的4GuysFromRolla.com,已在使用 Microsoft Web 技术,自1998开始。Scott Mitchell, author of seven ASP/ASP.NET books and founder of 4GuysFromRolla.com, has been working with Microsoft Web technologies since 1998. Scott 的工作方式是独立的顾问、培训师和撰稿人。Scott works as an independent consultant, trainer, and writer. 他的最新书籍是,在24小时内,sam ASP.NET 2.0。His latest book is Sams Teach Yourself ASP.NET 2.0 in 24 Hours. 可以mitchell@4GuysFromRolla.com访问。He can be reached at mitchell@4GuysFromRolla.com. 或通过他的博客,可以在http://ScottOnWriting.NET找到。or via his blog, which can be found at http://ScottOnWriting.NET.
特别感谢Special Thanks To
此教程系列由许多有用的审阅者查看。This tutorial series was reviewed by many helpful reviewers. 本教程的主管评审者是 Jackie Goor 和 Liz Shulok。Lead reviewers for this tutorial were Jackie Goor and Liz Shulok. 想要查看我即将发布的 MSDN 文章?Interested in reviewing my upcoming MSDN articles? 如果是这样,请在mitchell@4GuysFromRolla.com放置一行。If so, drop me a line at mitchell@4GuysFromRolla.com.