stand on the shoulders of giants

【ASP.NET Step by Step】之十六至二十三 Inserting, Updating, and Deleting Data

点击GridView的删除,事件触发过程

GridView控件提供了对行编辑和删除的内建的支持。配置一个GridView支持删除需要添加一个删除按钮列。当最终用户点击某一特定行的删除按钮时,引发一次回传并且GridView执行以下步骤: 

1.      ObjectDataSourceDeleteParameters赋值

2.      调用ObjectDataSourceDelete()方法,删除指定的记录

3.      通过调用它的Select()方法GridView重新绑定到ObjectDataSource  

赋值到DeleteParameters的值是点击删除按钮这一行的DataKeyNames字段的值。因此正确地设置GridViewDataKeyNames属性是至关重要的。如果缺少了这个,DeleteParameters将在第1步被赋上一个null值,从而在第2步中将不会导致删除任何记录。

点击GridView的编辑,事件触发过程

当最终用户点击特定一行的编辑按钮时,引发一次回传并且GridView执行以下步骤:  
     1. GridView的EditItemIndex属性被赋值为当前点击编辑按钮的行的索引
     2. 通过调用它的Select()方法,GridView重新绑定自己到ObjectDataSource
     3. 与EditItemIndex相匹配的行呈现为编辑模式。在此模式下,Edit按钮替换为Update和Cancel按钮,并且那些ReadOnly属性为False的绑定列呈现为
         TextBox服务器控件,这些TextBox的Text属性被赋值为相应的数据字段的值。 

到这里HTML标记被返回到浏览器,允许最终用户可以修改行数据。当用户点击保存按钮,再次发生一次回传,并且GridView执行以下几个步骤:
     1. ObjectDataSource的UpdateParameters的值被赋值为最终用户在GridView的编辑界面输入的值
     2. 调用ObjectDataSource的Update()方法,更新指定的记录
     3. 通过调用Select()方法,GridView重新绑定自己到ObjectDataSource
GridView的DataKeyNames属性指定的主键的值在第1步中赋值到UpdateParameters,反之非主键的值来自当前编辑行的TextBox服务器控件。如果DataKeyNames遗漏了,那么UpdateParameters主键的值在第1步中将被赋上一个空值,然后转入第2步中将不会导致任何记录的更新。
也可以这样说:基于GridView的DataKeyNames里的主键值和GridView里的值,组成ObjectDataSOurceUpdateParameters

如图所示,更新数据,触发一连串的Pre-和Post-事件


注意
我们创建的BLL层UpdateProduct方法是接受所有参数的,所以如果我们的GridView里绑定的列少了一些,
编辑的时候,这些没绑定的非只读列会被ObjectDataSource自做主张的置为NULL传给BLL层;
因此如果GridView少了ProductName,因为它是要求非空的,所以Update的时候就产生了异常。
所以,我们绑定几列,就应该在BLL层重载UpdateProduct(几列)方法
例如,我们在BLL重载了带三个参数的UpdateProduct方法,并且指定ObjectDataSource控件的Update方法为三个参数的UpdatProduct

<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>

这时,如果GridView是所有列,那么ObjectDataSource则会调用能接受这些参数的方法重载,
而不顾ObjectDataSource的声明标记指定只接受3个输入参数的事实。
而如果GridView含有4个列,则会在试图保存时引发一个异常
could not find a non-generic method 'UpdateProduct' that has parameters: ProductName, UnitPrice, ProductID, QuantityPerUnit.

注意Added by GridView

Pre级事件处理UnitPrice格式问题

UnitPrice的显示格式是{0:C}, 如果想让编辑时候也显示这个状态,要设置绑定列的ApplyFormatInEditMode属性为true,
这样设置后产生的新问题是当GridView尝试把用户提供的值赋值到ObjectDataSource的UpdateParameters集合,它无法把UnitPrice字符串“$19.00”转换成参数要求的decimal类型,

解决方法:

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;
 }
}

GridView的RowUpdating事件接受的第二个参数是一个GridViewUpdateEventArgs类型的对象,
它包含一个NewValues字典,当中的每一个属性保存着用户输入的值
e.Cancel=true;  说明当用户忘记输入UnitPrice,则显示提示信息,并Cancel掉更新。

Bind() 和 Eval()

Code

<%# Bind("dataField") %>   双向绑定,
用户点击编辑按钮,Bind()绑定数据到模版(TextBox),
点击Update按钮,通过Bind()指定的数据字段的值被回传到ObjectDataSource的UpdateParameters
<%# Eval("dataField") %>   单向绑定,
仅仅在绑定数据到模版时取得数据字段的值,但并不会在回传时将用户输入的值返回到数据源控件的参数

Post级事件用于异常处理

1. 如果数据库不正常运作,则在试图连接数据库时通过TableAdapter抛出一个SqlException异常。
2. DAL层异常,遗漏了ProductName值则引发抛出一个NoNullAllowedException异常,因为ProdcutsRow类的ProductName属性设置了它的AllowDBNull属性为false(这句话好像有点问题)
3. BLL层异常,

数据库这样的设置 .NET在创建DataSet时做了什么呢?

.NET为我们做了:

public partial class ProductsRow : global::System.Data.DataRow {
     
private ProductsDataTable tableProducts;

     [
global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
      
public string ProductName {
            
get {
                
return ((string)(this[this.tableProducts.ProductNameColumn]));
            }
            
set {
                
this[this.tableProducts.ProductNameColumn] = value;
            }
      }
    
//
}

public partial class ProductsDataTable : global::System.Data.TypedTableBase<ProductsRow> {
    
private global::System.Data.DataColumn columnProductName;

   [
global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        
public ProductsDataTable() {
            
this.TableName = "Products";
            
this.BeginInit();
            
this.InitClass();
            
this.EndInit();
        }
   
//
   [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
   
public global::System.Data.DataColumn ProductNameColumn {
            
get {
                
return this.columnProductName;
            }
   }
}

[
global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
private void InitClass() {
this.columnProductName.AllowDBNull = false;
this.columnProductName.MaxLength = 40;
//
}

够复杂吧

还是回到主题,没有任何的动作,这些异常都会从数据访问层冒出到业务逻辑层,然后到ASP.NET页面,最后到ASP.NET运行时
用户是不希望看到这样的东西的。人性化的方法是我们提前处理这些异常,给用户友好的界面。
ObjectDataSource 和数据Web控件的post级事件都提供了发现并不让它出现在ASP.NET运行时的方法
下面的代码实现了上述的目的:

 1protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
 2{
 3    if (e.Exception != null)
 4    {
 5        // Display a user-friendly message
 6        ExceptionDetails.Visible = true;
 7        ExceptionDetails.Text = "There was a problem updating the product. ";
 8
 9        if (e.Exception.InnerException != null)
10        {
11            Exception inner = e.Exception.InnerException;
12
13            if (inner is System.Data.Common.DbException)
14                ExceptionDetails.Text += "Our database is currently experiencing problems. Please try again later.";
15            else if (inner is NoNullAllowedException)
16                ExceptionDetails.Text += "There are one or more required fields that are missing.";
17            else if (inner is ArgumentException)
18            {
19                string paramName = ((ArgumentException)inner).ParamName;
20                ExceptionDetails.Text += string.Concat("The ", paramName, " value is illegal.");
21            }

22            else if (inner is ApplicationException)ExceptionDetails.Text += inner.Message;
23        }

24
25        // Indicate that the exception has been handled
26        e.ExceptionHandled = true;
27
28        // Keep the row in edit mode
29        e.KeepInEditMode = true;
30    }

31}

32

可以看出来,如果UnitPrice小于0,产生的异常,在这里被捕获了

// UnitPrice小于0,抛出异常

public partial class Northwind
{
    
public partial class ProductsDataTable
    {
        
public override void BeginInit()
        {
            
this.ColumnChanging += ValidateColumn;
        }

        
void ValidateColumn(object sender, DataColumnChangeEventArgs e)
        {
            
if (e.Column.Equals(this.UnitPriceColumn))
            {
                
if (!Convert.IsDBNull(e.ProposedValue) && (decimal)e.ProposedValue < 0)
                {
                    
throw new ArgumentException("UnitPrice cannot be less than zero""UnitPrice");
                }
            }
            
else if (e.Column.Equals(this.UnitsInStockColumn) ||
                    e.Column.Equals(
this.UnitsOnOrderColumn) ||
                    e.Column.Equals(
this.ReorderLevelColumn))
            {
                
if (!Convert.IsDBNull(e.ProposedValue) && (short)e.ProposedValue < 0)
                {
                    
throw new ArgumentException(string.Format("{0} cannot be less than zero", e.Column.ColumnName),e.Column.ColumnName);
                }
            }
        }

    }
}


BLL层面的异常也在这里被捕获
我们给UpdateProduct重载增加一个业务规则:禁止把UnitPrice字段的值设置为超过原来的两倍。为了实现这一点,调整UpdateProduct重载以使它可以执行这个检查并且在违反该规则时抛出一个ApplicationException异常。
 // Make sure the price has not more than doubled
11    if (unitPrice != null && !product.IsUnitPriceNull())
12        if (unitPrice > product.UnitPrice * 2)
13          throw new ApplicationException("When updating a product price," + " the new price cannot exceed twice the original price.");
BLL引发的ApplicationException异常在GridView的RowUpdated事件处理程序中被侦测并处理。

验证控件

对比上面对UnitPrice<0的处理,抛出异常,在RowUpdated捕获;
一般更常用的是增加验证控件。

例如给ProductName的EditItemTemplate添加RequiredFieldValidator验证控件,
RequiredFieldValidator的ControlToValidate属性为EditProductName。最后,设置ErrorMessage属性为“You must provide the product’s name” 并将Text属性设置为“*”。
例如给UnitPrice 的EditItemTemplate模板增加CompareValidator验证控件
设置CompareValidator控件ControlToValidate属性为TextBoxID,Operator属性为GreaterThanEqual,ValueToCompare属性为 “0”, 并且Type属性为Currency,ErrorMessage属性为“The price must be greater than or equal to zero and cannot include the currency symbol”,

ASP.NET包含了一个总结控件ValidationSummary control,可以显示那些检测到无效数据的验证控件的ErrorMessage。以文本方式在页上某个位置概述错误结果,或者通过一个客户端消息框。
设置其ShowSummary属性为false并设置ShowMessageBox属性为true。不需要设置其他什么属性。这样,所有的验证错误都会显示在一个客户端消息框中。

对验证控件要分组
默认情况下,当postback发生时页面上所有的验证都会生效。显然,当编辑GridView记录时我们不希望DetailsView新增功能的验证起作用,
尴尬局面-当用户在编辑product时输入了有效数据,在点击更新时却由于新增功能中的name和price空白而产生验证错误。
例如将GridView中CommandField的ValidationGroup属性则指定为EditValidationControls(直接输入),GridView EditItemTemplate中可以指定的,ValidationGroup属性统一设置为EditValidationControls,验证控件的ValidationGroup属性设置为EditValidationControls。
验证组中的验证控件只在有相同ValidationGroup属性的按钮产生postback时才会进行有效性检测
对于ValidationSummary控件也要改变
由于ValidationSummary控件也拥有ValidationGroup属性并且只显示来自于同一验证组中验证控件的信息。因此,我们需要使用两个验证控件,分别作为InsertValidationControls验证组和EditValidationControls验证组: 

<asp:ValidationSummary ID="ValidationSummary1" runat="server" ShowMessageBox="True"
ShowSummary
="False" ValidationGroup="EditValidationControls" />
<asp:ValidationSummary ID="ValidationSummary2" runat="server" ShowMessageBox="True"
ShowSummary
="False" ValidationGroup="InsertValidationControls" />

 

SupplierName列改为DropDownList

只要将其转化为模板,把EditTemplateItem改为DropDownList,设置绑定的数据源,绑定字段。
Product表中的CategoryID 和 SupplierID列允许为NULL,而编辑模板中的下拉列表却没有NULL这一项。所以存在下面两种问题:
1. 用户无法则现在的界面中将某个product非空的category或supplier设置为NULL
2. 如果产品的CategoryID 或 SupplierID为NULL,在点击Edit按钮时程序会抛出异常。这是因为Bind()表达式中CategoryID(或SupplierID)返回NULL值时,SelectedValue无法找到NULL这一列表项因而抛出异常。 
为了支持CategoryID 和 SupplierID的NULL值,需要为两个DropDownList增加一个NULL值选项。方法是将DropDownList的AppendDataBoundItems属性设置为true并手动增加一个值为空字符串的列表项。在ASP.NET的数据绑定逻辑中,空字符串将自动转换为NULL,NULL值也可以转为空字符串。因此,先元素标记大致如下:

<asp:DropDownList ID="Categories" runat="server" DataSourceID="CategoriesDataSource" DataTextField="CategoryName" DataValueField="CategoryID" SelectedValue='<%# Bind("CategoryID") %>' AppendDataBoundItems="True">
<asp:ListItem Value="">(None)</asp:ListItem>
</asp:DropDownList>

 

开放式并发

此处

删除操作的客户端确认

JavaScript的confirm(string)方法
在一个模式窗口中显示那些作为string参数传进来的文本,这个窗口将会显示两个按钮-确定(OK)和取消(Cancel)。
根据点击不同的按钮来返回一个布尔类型值。(如果点击了OK,返回true;如果点击Cancel,返回false)

ASP.NET 2.0为为Button,LinkButton,ImageButton新引入的OnClientClick这个属性,可以通过它为客户端单击事件添加客户端脚本。
如果onClinetClick=“True"则执行单击,false则放弃。

<asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Delete" 
OnClientClick
="return confirm('Are you certain that you want to delete this product?');">
</asp:LinkButton>


GridView或者DetailsView上内置的一些删除按钮,怎么处理呢?
方法1. 把他们转化为Template,就变成了按钮,照上面做
方法2. DataBound事件中进行绑定,而且还可以访问字段,增加ProducName

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    
if (e.Row.RowType == DataControlRowType.DataRow)
    
{
        
// reference the Delete LinkButton
        LinkButton db 
= (LinkButton)e.Row.Cells[0].Controls[0]; 

        
// Get information about the product bound to the row
        Northwind.ProductsRow product 
= (Northwind.ProductsRow) ((System.Data.DataRowView) e.Row.DataItem).Row; 

        db.OnClientClick 
= string.Format("return confirm('Are you certain you want to delete the {0} product?');"

        product.ProductName.Replace(
"'"@"\'"));
    }

}

 

几点注意:
1.  如果有Select,Delete,则用e.Row.Cells[0].Contorls[2],  
     因为在删除按钮前面有两个控件,一个是编辑按钮,另一个是LiteralControl,用来隔离编辑按钮和删除按钮。
2.  这里用Index访问控件,不变之处是 如果有人增加了Select,Edit,或是改变了GridView的按钮类型,编译没问题,点击就会异常;而且修改起来很麻烦;
     所以常常把他们转为Template,变为真的按钮。
3.  为什么不用product.ProductName
     因为可能有产品名称为Duck's bag,这样程序产生运行时错误: error:Expected')'   [拼”return confirm('...‘);"时候产生错误]
     所以把所有产品名中的' 转为 \'

所以最好的做法是:
1. 把GridView的Delete转为Template,设置Delete按钮的ID为“DeleteBtn”
2. RowDataBound事件

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        
if (e.Row.RowType == DataControlRowType.DataRow)
        {
            LinkButton db 
= (LinkButton)e.Row.FindControl("DeleteBtn");

            Northwind.ProductsRow product 
= (Northwind.ProductsRow)((System.Data.DataRowView)e.Row.DataItem).Row;

            db.OnClientClick 
= string.Format("return confirm('Are you certain you want to delete the {0} product?');",
                product.ProductName.Replace(
"'",@"\'"));
        }

    }

 

根据登录用户而定制不同的数据修改界面

1. 登陆用户不同,绑定不同的DataSouce实现不同的界面
2. 根据某项Data,设置访问权限,例如被废弃的产品将是不可编辑的,

Northwind.ProductsRow product = (Northwind.ProductsRow)((System.Data.DataRowView)e.Row.DataItem).Row;
if (product.Discontinued)
{ LinkButton editbutton = (LinkButton)e.Row.FindControl("EditLinkButton");
  editbutton.Visible = false;}


 

posted @ 2008-12-22 17:36  DylanWind  阅读(748)  评论(0编辑  收藏  举报