在ASP.NET 2.0中操作数据::概述插入、更新和删除数据
      
    
    
      
        导言
      
    
    
      
   结束前面的几节,我们已经探讨过了如何使用GridView、DetailsView和FormView控件来显示数据。这些控件简单地操作提供给它的数据。一般地,这些控件通过使用一个数据源控件,例如ObjectDataSource来存取数据。我们已经看过了ObjectDataSource是如何在ASP.NET页面和潜在的数据之间扮演一个代理的角色。当一个GridView需要显示数据时,它调用ObjectDataSource的 
  
      
        Select()
      
      方法,这个方法转而调用一个来自我们的业务逻辑层(BLL)的方法,继而调用一个适当的数据访问层(DAL)的表适配器(TableAdapter)的方法,从而它发送一个 
  
      
        SELECT
      
      查询到Northwind数据库。 
 
    
    
      
   记得在我们的教程里当创建DAL中的表适配器时,Visual Studio自动地添加从潜在数据库插入、更新和删除数据的方法。此外,在 
  
      
        
          
            
              创
              
                建一个业务逻辑层
              
            
          
        
      
      这一节我们已经设计了调用这些数据更改的DAL方法的BLL方法。 
 
    
    
      
   除了它的 
  
      
        Select()
      
      方法,ObjectDataSource还有 
  
      
        Insert()
      
      、 
  
      
        Update()
      
      和 
  
      
        Delete()
      
      方法。跟 
  
      
        Select()
      
      方法类似,这三个方法映射到一个隐含的对象。当配置插入、更新或删除数据时,GridView、DetailsView和FormView控件提供了一个修改潜在的数据的用户界面。这个用户界面调用ObjectDataSource的 
  
      
        Insert()
      
      
        、
      
      
        Update()
      
      和 
  
      
        Delete()
      
      方法,它们继而调用隐含对象的关联方法(见图1)。 
 
    
    
    
      
   本节我们将看看如何映射ObjectDataSource的 
  
      
        Insert()
      
      、 
  
      
        Update()
      
      和 
  
      
        Delete()
      
      方法到BLL中的类,也看看如何配置GridView、DetailsView和FormView控件提供修改数据的功能。 
 
    
    
      
        第一步: 创建Insert、Update和Delete教程页面
      
    
    
      
   在我们开始探讨如何插入、修改和删除数据之前,让我们先花些时间在我们的站点项目里添加这些本节里和下一节里需要的ASP.NET页面。首先添加一个名为 
  
      
        EditInsertDelete
      
      的新文件夹。然后,在这个文件夹里添加下面这些ASP.NET页面,并且确认每个页面都关联 
  
      
        Site.master
      
      母版页: 
 
    
    
      
        ·
        
        
      
      
        Default.aspx
      
    
    
      
        ·
        
        
      
      
        Basics.aspx
      
    
    
      
        ·
        
        
      
      
        DataModificationEvents.aspx
      
    
    
      
        ·
        
        
      
      
        ErrorHandling.aspx
      
    
    
      
        ·
        
        
      
      
        UIValidation.aspx
      
    
    
      
        ·
        
        
      
      
        CustomizedUI.aspx
      
    
    
      
        ·
        
        
      
      
        OptimisticConcurrency.aspx
      
    
    
      
        ·
        
        
      
      
        ConfirmationOnDelete.aspx
      
    
    
      
        ·
        
        
      
      
        UserLevelAccess.aspx
      
    
    
    
      
   类似在其它文件夹里, 
  
      
        EditInsertDelete
      
      文件夹里的 
  
      
        Default.aspx
      
      将列出这些教程章节。记得用户控件提供这个功能。因此,从解决方案资源管理器中拖拽一个这个用户控件到页面的设计视图,从而添加它到 
  
      
        Default.aspx
      
      页面。 
 
    
    
    
      
   最后,添加这些页面地址项到 
  
      
        Web.sitemap
      
      文件。明确地,在Customized Formatting 
  
      
         <siteMapNode>
      
      后添加如下标记: 
 
    
    
      
        
          <siteMapNode title="Editing, Inserting, and Deleting" url="~/EditInsertDelete/Default.aspx" description="Samples of Reports that Provide Editing, Inserting, and Deleting Capabilities">
        
      
    
    
      
        
          <siteMapNode url="~/EditInsertDelete/Basics.aspx" title="Basics" description="Examines the basics of data modification with the GridView, DetailsView, and FormView controls." />
        
      
    
    
      
        
          <siteMapNode url="~/EditInsertDelete/DataModificationEvents.aspx" title="Data Modification Events" description="Explores the events raised by the ObjectDataSource pertinent to data modification." />
        
      
    
    
      
        
          <siteMapNode url="~/EditInsertDelete/ErrorHandling.aspx" title="Error Handling" description="Learn how to gracefully handle exceptions raised during the data modification workflow." />
        
      
    
    
      
        
          <siteMapNode url="~/EditInsertDelete/UIValidation.aspx" title="Adding Data Entry Validation" description="Help prevent data entry errors by providing validation." />
        
      
    
    
      
        
          <siteMapNode url="~/EditInsertDelete/CustomizedUI.aspx" title="Customize the User Interface" description="Customize the editing and inserting user interfaces." />
        
      
    
    
      
        
          <siteMapNode url="~/EditInsertDelete/OptimisticConcurrency.aspx" title="Optimistic Concurrency" description="Learn how to help prevent simultaneous users from overwritting one another's changes." />
        
      
    
    
      
        
          <siteMapNode url="~/EditInsertDelete/ConfirmationOnDelete.aspx" title="Confirm On Delete" description="Prompt a user for confirmation when deleting a record." />
        
      
    
    
      
        
          <siteMapNode url="~/EditInsertDelete/UserLevelAccess.aspx" title="Limit Capabilities Based on User" description="Learn how to limit the data modification functionality based on the user's role or permissions." />
        
      
    
    
      
        
          </siteMapNode>
        
      
    
    
      
   在更新了 
  
      
        Web.sitemap
      
      后,花些时间通过浏览器访问本教程站点。左边的菜单里现在包含对应编辑、插入和删除教程的项。 
 
    
    
    
      
        第二步: 添加并配置ObjectDataSource控件
      
    
    
      
   因为GridView、DetailsView和FormView控件在数据修改功能和版面上都有所不同,就让我们逐个研究。不过,与其让这三个控件各自使用自己的ObjectDataSource,还不如让我们仅创建一个ObjectDataSource让这个三个控件的例子共用。 
 
    
    
      
   打开 
  
      
        Basics.aspx
      
      页面,从工具箱拖拽一个ObjectDataSource到设计器,从它的职能标记中点击配置数据源链接。因为 
  
      
        ProductsBLL
      
      类是唯一一个提供修改、插入和删除方法的BLL类,配置该ObjectDataSource使用这个类。 
 
    
    
    
      
   在下一屏中,通过选择适当的tab页并从下拉列表中选择方法,我们可以指定 
  
      
        ProductsBLL
      
      类里的哪些方法被映射到ObjectDataSource的 
  
      
        Select()
      
      、 
  
      
        Insert()
      
      、 
  
      
        Update()
      
      和 
  
      
        Delete()
      
      方法。图6,至今我们应该很熟悉,映射ObjectDataSource的 
  
      
        Select()
      
      方法到 
  
      
        ProductsBLL
      
      类的 
  
      
        GetProducts()
      
      方法。 
  
      
        Insert()
      
      、 
  
      
        Update()
      
      和 
  
      
        Delete()
      
      方法可以通过选择上方的适当的tab页进行配置。 
 
    
    
    
      
   图7、8和9显示ObjectDataSource的UPDATE、INSERT,和DELETE 的tab页。配置它们从而 
  
      
        Insert()
      
      、 
  
      
        Update()
      
      和 
  
      
        Delete()
      
      方法分别调用 
  
      
        ProductsBLL
      
      类的 
  
      
        UpdateProduct
      
      、 
  
      
        AddProduct
      
      和 
  
      
        DeleteProduct
      
      方法。 
 
    
    
    
    
    
      
   你也许已经注意到在UPDATE、INSERT和DELETE的tab页里的下拉列表中已经选择了各自的方法。这是由于我们使用了 
  
      
        DataObjectMethodAttribute
      
      ,它修饰了 
  
      
        ProducstBLL
      
      类。例如,DeleteProduct方法是如下这样子声明的: 
 
    
    
      
        
          [System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Delete, true)]
        
      
    
    
      
        
          public bool DeleteProduct(int productID)
        
      
    
    
      
        
          {
        
      
    
    
      
        
          
             ...
          
        
      
    
    
      
        
          }
        
      
    
    
      
        DataObjectMethodAttribute
      
      指示每一个方法的目的–是否为了查询、插入、更新或删除– 
 
    
    
      
   是否它的默认值。如果你在创建BLL类的时候省略了这些属性,现在你将需要手工从UPDATE、INSERT和DELETE的tab页里手工选择方法。 
 
    
    
      
   当确认已经适当的 
  
      
        ProductsBLL
      
      方法映射到ObjectDataSource的 
  
      
        Insert()
      
      、 
  
      
        Update()
      
      和 
  
      
        Delete()
      
      方法后,点击完成结束此向导。 
 
    
    
      
        检查ObjectDataSource的标记
      
    
    
      
   在通过数据源配置向导完成了对ObjectDataSource的配置之后,到源视图去检查一下生成的声明标记。 
  
      
        <asp:ObjectDataSource>
      
      标签列明了隐含的对象和需要调用的方法。另外,还有 
  
      
        DeleteParameters
      
      、 
  
      
        UpdateParameters
      
      和 
  
      
        InsertParameters
      
       ,它们映射 
  
      
        ProductsBLL
      
      类的 
  
      
        AddProduct
      
      、 
  
      
        UpdateProduct
      
      和 
  
      
        DeleteProduct
      
      方法的输入参数: 
 
    
    
      
        
          <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="DeleteProduct"
        
      
    
    
      
        
          
             InsertMethod="AddProduct" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
          
        
      
    
    
      
        
          
             TypeName="ProductsBLL" UpdateMethod="UpdateProduct">
          
        
      
    
    
      
        
          
             <DeleteParameters>
          
        
      
    
    
      
        
          
             <asp:Parameter Name="productID" Type="Int32" />
          
        
      
    
    
      
        
          
             </DeleteParameters>
          
        
      
    
    
      
        
          
             <UpdateParameters>
          
        
      
    
    
      
        
          
             <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" />
          
        
      
    
    
      
        
          
             <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>
        
      
    
    
      
   ObjectDataSource包含了对应它关联的方法的每一个输入参数的parameter,就像当ObjectDataSource被配置为调用预期一个输入参数的查询方法(例如 
  
      
        GetProductsByCategoryID(
        
          categoryID
        
        )
      
      )时出现的 
  
      
        SelectParameter
      
      s一栏。正如我们马上即将看到的,这些 
  
      
        DeleteParameters
      
      、 
  
      
        UpdateParameters
      
      和 
  
      
        InsertParameters
      
      的值在调用ObjectDataSource的 
  
      
        Insert()
      
      、 
  
      
        Update()
      
      或 
  
      
        Delete()
      
      方法之前自动地通过GridView、DetailsView和FormView被设置。必要时这些值也可以通过编程设置,这在以后的章节里讨论。 
 
    
    
      
   使用数据源配置向导来配置ObjectDataSource的另一个影响是Visual Studio设置了 
  
      
        OldValuesParameterFormatString
      
      属性为 
  
      
        original_{0}
      
      。这个属性值用来包含数据被编辑时的原始值,它在下面两种情况下非常有用: 
 
    
    
      
        ·
        
        
      
      如果,当编辑一条记录时,用户可以修改主键的值。在这种情况下,新的主键的值和原始的主键值都需要提供,这样具有这个原始主键值的数据库记录才可以被找到然后才能将它的值更新。 
 
    
    
      
        ·
        
        
      
      当使用开放式并发。开放式并发是为了保证同时操作的用户不至于覆盖另一个用户所做更改的一种技巧,这也是后面的教程中的一节( 
  
      
        
          
            
              实现开放式并发
            
          
        
      
       )。 
 
    
    
      
   这个 
  
      
        OldValuesParameterFormatString
      
      属性指明了隐含对象的更新和删除方法中对应原始值的输入参数的名称。我们将在探讨开发式并发的时候更详细地讨论这个属性和它的目的。不过暂时我放下它,因为我们的BLL的方法并不需要这些原始的值因此我们删除这个属性,这一点很重要。如果让 
  
      
        OldValuesParameterFormatString
      
      属性设置为除了默认值( 
  
      
        {0}
      
      )以外的其它任何的值,都将在数据Web控件尝试调用ObjectDataSource的 
  
      
        Update()
      
      或 
  
      
        Delete()
      
      方法时引发一个错误,因为ObjectDataSource将尝试将这些原始值参数与 
  
      
        UpdateParameters
      
      或 
  
      
        DeleteParameters
      
      一起传入。 
 
    
    
      
   如果对此不是十分清楚,别担心,我们将在未来的章节中研究这个属性和它的效用。暂时,一定要完全地从声明语法中完全地删除这个属性或者将它设置为默认值( 
  
      
        {0}
      
      )。 
 
    
    
      
        注意
      
      
        :
      
       如果你只是简单地从设计视图的属性窗口删除这个 
  
      
        OldValuesParameterFormatString
      
      属性的值,这个属性依旧会存在于声明语法中,不过被设置为一个空字符串。不幸地,这将依旧导致上面提到的同样的问题。所以,从声明语法里彻底地删除这个属性,或者从属性窗口将其设置为默认值, 
  
      
        {0}
      
      。 
 
    
    
      
        第三步: 添加一个数据Web服务器控件并配置它为数据更改服务
      
    
    
      
   一般ObjectDataSOurce被添加到页面并配置完成,我们可以添加一个数据Web服务器控件用来显示数据并提供一个最终用户修改数据的途径。我们将分别看看GridView、DetailsView和FormView,因为这些数据Web服务器控件在它们的数据更改功能和配置上都有所不同。 
 
    
    
      
   正如我们将在本文剩下的部分里看到的,通过GridView、DetailsView和FormView控件添加一个非常基本的编辑、插入和删除支持是真的非常简单,只需要勾选上一对CheckBox。现实中提供这样的功能有许多微妙之处和边缘案例,这要比仅仅点几下要棘手得多。但是,本教程里,只着眼于提供简单的数据修改功能。以后的章节将研究在现实中不容置疑地出现的问题。 
 
    
    
      
        从GridView中删除数据
      
    
    
      
   首先,从工具箱拖拽一个GridView到设计器。然后,通过GridView的智能标记中从下拉列表中选择从而绑定ObjectDataSource到该GridView。在这里GridView的声明标记将是: 
 
    
    
      
        
          <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False">
        
      
    
    
      
        
          
             <Columns>
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False"
          
        
      
    
    
      
        
          
             ReadOnly="True" SortExpression="ProductID" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" />
          
        
      
    
    
      
        
          
             <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" ReadOnly="True"
          
        
      
    
    
      
        
          
             SortExpression="CategoryName" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" ReadOnly="True"
          
        
      
    
    
      
        
          
             SortExpression="SupplierName" />
          
        
      
    
    
      
        
          
             </Columns>
          
        
      
    
    
      
        
          </asp:GridView>
        
      
    
    
      
   通过它的职能标记绑定GridView到ObjectDataSource有下面两点的好处: 
 
    
    
      
        ·
        
        
      
      绑定列和CheckBox列被自动地添加,对应ObjectDataSource返回的每一个字段。而且,这些绑定列和CheckBox列的属性已经被设置,基于隐含字段的元数据。例如 
  
      
        ProductID
      
      、 
  
      
        CategoryName
      
      和 
  
      
        SupplierName
      
      列在 
  
      
        ProductsDataTable
      
      里被标记为只读,因此它们在编辑时也是不可更新的。为了实现这一点,这些绑定列的 
  
      
        ReadOnly
      
      属性设置为 
  
      
        true
      
       。 
 
    
    
      
        ·
        
        
      
      
        DataKeyNames
      
      属性被赋值为隐含对象的主键。这是在使用GridView来编辑或删除数据的要点,因为这个属性象指出了标识唯一记录的那个字段(或是一组字段)。如果要获得更多的关于 
  
      
        DataKeyNames
      
      属性的信息,请回到 
  
      
        
          
            
              使用
            
            GridView 
            
              和
            
            DetailView
            
              实现的主
            
            /
            
              从报表
            
          
        
      
      一节。 
 
    
    
      
   虽然可以通过属性窗口或者声明语法将GridView绑定到ObjectDataSource,不过这需要你手工添加适当的绑定列和 
  
      
        DataKeyNames
      
      标记。 
 
    
    
      
   GridView控件提供了对行编辑和删除的内建的支持。配置一个GridView支持删除需要添加一个删除按钮列。当最终用户点击某一特定行的删除按钮时,引发一次回传并且GridView执行以下步骤: 
 
    
    
      
        1.
        
        
      
      对ObjectDataSource的 
  
      
        DeleteParameters
      
      赋值 
 
    
    
      
        2.
        
        
      
      调用ObjectDataSource的 
  
      
        Delete()
      
      方法,删除指定的记录 
 
    
    
      
        3.
        
        
      
      通过调用它的 
  
      
        Select()
      
      方法GridView重新绑定到ObjectDataSource 
 
    
    
      
   赋值到 
  
      
        DeleteParameters
      
      的值是点击删除按钮这一行的 
  
      
        DataKeyNames
      
      字段的值。因此正确地设置GridView的 
  
      
        DataKeyNames
      
      属性是至关重要的。如果缺少了这个, 
  
      
        DeleteParameters
      
      将在第1步被赋上一个 
  
      
        null
      
      值,从而在第2步中将不会导致删除任何记录。 
 
    
    
      
   为了给GridView增加删除功能,简单地到它的职能标记里勾选上“启用删除”。 
 
    
    
    
      
   从智能标记中勾选启用删除会添加一个CommandField到GridView。这个CommandField在GridView中补充一个按钮列,它履行一个或多个下属任务:选中一行记录、编辑一行记录和删除一行记录。我们先前在 
  
      
        
          
            
              使用
            
            GridView 
            
              和
            
            DetailView
            
              实现的主
            
            /
            
              从报表
            
          
        
      
      一节的教程里也看到过了CommandField用作选中记录时如何运作。 
 
    
    
      
   这个CommandFIeld包含了一些 
  
      
        Show
        
          X
        
        Button
      
      属性,它指示哪一系列的按钮显示在CommandField中。通过勾选启用删除,一个 
  
      
        ShowDeleteButton
      
      属性为 
  
      
        true
      
      的CommandField被添加到GridView的列集合。 
 
    
    
      
        
          <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False">
        
      
    
    
      
        
          
             <Columns>
          
        
      
    
    
      
        
          
             <asp:CommandField ShowDeleteButton="True" />
          
        
      
    
    
      
        
          
             ... BoundFields removed for brevity ...
          
        
      
    
    
      
        
          
             </Columns>
          
        
      
    
    
      
        
          </asp:GridView>
        
      
    
    
      
   到这里,你可能还不能相信,我们已经完成了给这个GridView增加删除支持!正如图11显示的,当我们通过浏览器访问此页面时,一列删除按钮已经出现。 
 
    
    
    
      
   如果你是从一开始就是自己创建本教程的程序,当测试这个页面点击删除按钮时将引发一个异常。继续读下去学习关于为什么会引发这些异常,还有如何修复它们。 
 
    
    
      
        注意
      
      
        : 
      
      如果你是跟随着下载的教程的程序,这些问题已经被解决。然而,我鼓励你从头到尾读一遍下面列出的详细资料来帮助你识别可能出现的问题和适宜的工作区。 
 
    
    
      
   如果,当尝试删除一个产品,你得到一个类似于“ 
  
      
        ObjectDataSource 'ObjectDataSource1' could not find a non-generic method 'DeleteProduct' that has parameters: productID, original_ProductID
      
      ,”的异常,你可能忘记了从ObjectDataSource里删除 
  
      
        OldValuesParameterFormatString
      
      属性。指定了 
  
      
        OldValuesParameterFormatString
      
      属性的话,该ObjectDataSource会试图向 
  
      
        DeleteProduct
      
      方法一并传入 
  
      
        productID
      
      和 
  
      
        original_ProductID
      
      输入参数,然而, 
  
      
        DeleteProduct
      
      方法只能接受一个输入参数,导致异常。删除 
  
      
        OldValuesParameterFormatString
      
      属性(或设置它为 
  
      
        {0}
      
      )指示ObjectDataSource不要试图传入这个原始值的输入参数。 
 
    
    
    
      
   即使你已经删除了 
  
      
        OldValuesParameterFormatString
      
      属性,当你尝试删除一个产品时依旧将得到一个异常:“ 
  
      
        The DELETE statement conflicted with the REFERENCE constraint 'FK_Order_Details_Products'
      
      .”。Northwind数据库包含了一个在 
  
      
        Order Details
      
      和 
  
      
        Products
      
      表间的字段约束,表示如果一个产品在 
  
      
        Order Details
      
      表里对应它有一条或多条记录,那么该产品不能被删除。因为Northwind数据库里的每一个产品在 
  
      
        Order Details
      
      表里都至少有一条记录,所以我们不能删除任何产品,除非我们从order details表里删除这个产品的关联记录。 
 
    
    
    
      
   为了我们的教程,就让我们删除 
  
      
        Order Details
      
      表里的所有记录吧。在一个真实的应用程序中我们需要的是下面任一措施: 
 
    
    
      
        ·
        
        
      
      通过另外一个页面管理order details信息 
 
    
    
      
        ·
        
        
      
      在 
  
      
        DeleteProduct
      
      方法里增加包含删除指定产品的订单明细的逻辑 
 
    
    
      
        ·
        
        
      
      修改TableAdapter所使用的SQL语句,包含对指定产品的订单明细的删除 
 
    
    
      
        就让我们从
      
      
        Order Details
      
      
        表里删除所有记录从而绕过字段间约束的问题。到
      
      Visual Studio的服务器资源管理器,在 
  
      
        NORTHWND.MDF
      
      节点上点击鼠标右键,选择“新建查询”。然后,再查询窗口执行下面的SQL语句: 
  
      
        DELETE FROM [Order Details]
        
      
    
    
    
      
   在清空了 
  
      
        Order Details
      
      表后,点击删除按钮将会正确无误地删除这个产品。如果点击了删除按钮但是没有删除该产品,检查并确保GridView的 
  
      
        DataKeyNames
      
      属性设置为主键( 
  
      
        ProductID
      
      )。 
 
    
    
      
        注意
      
      
        :
      
      当点击删除按钮时引发一次回传并删了了该记录。这是危险的,因为它很容易意外地错误点击了别的行的删除按钮。以后的章节里我们将看看如何在删除记录时添加一个客户端的确认询问。 
 
    
    
      
        在GridView中编辑数据
      
    
    
      
   跟删除支持一起,GridView还提供了内建的对行编辑的支持。配置GrdiView支持编辑将添加一列编辑按钮。从最终用户的角度,点击一行的编辑按钮可使这一行变成可编辑的,它的单元格转换成文本框并包含现有的值,并把编辑按钮替换成保存和取消按钮。在完成了他们期望的更改之后,最终用户可以点击保存按钮提交这些修改,或者点击取消按钮放弃这些修改。在任意一种情况,点击保存或者取消按钮后GridView回到它编辑前的状态。 
 
    
    
      
   站在我们页面开发者的角度,当最终用户点击特定一行的编辑按钮时,引发一次回传并且GridView执行以下步骤: 
 
    
    
      
        1.
        
        
      
      GridView的 
  
      
        EditItemIndex
      
      属性被赋值为当前点击编辑按钮的行的索引 
 
    
    
      
        2.
        
        
      
      通过调用它的 
  
      
        Select()
      
      方法,GridView重新绑定自己到ObjectDataSource 
 
    
    
      
        3.
        
        
      
      与 
  
      
        EditItemIndex
      
      相匹配的行呈现为编辑模式。在此模式下,编辑按钮替换为保存和取消按钮,并且那些 
  
      
        ReadOnly
      
      属性为False的绑定列呈现为TextBox服务器控件,这些TextBox的 
  
      
        Text
      
      属性被赋值为相应的数据字段的值。 
 
    
    
      
   到这里HTML标记被返回到浏览器,允许最终用户可以修改行数据。当用户点击保存按钮,再次发生一次回传,并且GridView执行以下几个步骤: 
 
    
    
      
        1.
        
        
      
      ObjectDataSource的 
  
      
        UpdateParameters
      
      的值被赋值为最终用户在GridView的编辑界面输入的值 
 
    
    
      
        2.
        
        
      
      调用ObjectDataSource的 
  
      
        Update()
      
      方法,更新指定的记录 
 
    
    
      
        3.
        
        
      
      通过调用它的 
  
      
        Select()
      
      方法,GridView重新绑定自己到ObjectDataSource 
 
    
    
      
   在 
  
      
        DataKeyNames
      
      属性指定的主键的值在第1步中赋值到 
  
      
        UpdateParameters
      
      ,反之非主键的值来自当前编辑行的TextBox服务器控件。如果这一点遗漏了,那么 
  
      
        UpdateParameters
      
      主键的值在第1步中将被赋上一个值,然后转入第2步中将不会导致任何记录的更新。 
 
    
    
      
   编辑功能可以简单地通过勾选GridView的智能标记中的启用编辑从而被激活。 
 
    
    
    
      
   勾选启用编辑将添加一个CommandField(如果需要的话)并设置它的 
  
      
        ShowEditButton
      
      属性为 
  
      
        true
      
       。如我们之前所看过的,CommandField包含一些 
  
      
        Show
        
          X
        
        Button
      
      属性,他们指出哪一系列的按钮要显示在CommandField里。在我们的例子里,勾选启用编辑添加 
  
      
        ShowEditButton
      
      属性到现有的CommandField里: 
 
    
    
      
        
          <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False">
        
      
    
    
      
        
          
             <Columns>
          
        
      
    
    
      
        
          
             <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
          
        
      
    
    
      
        
          
             ... BoundFields removed for brevity ...
          
        
      
    
    
      
        
          
             </Columns>
          
        
      
    
    
      
        
          </asp:GridView>
        
      
    
    
      
   就这样就添加了根本的编辑支持。如图16所示,编辑界面还比较粗糙–每一个非只读的绑定列呈现为一个TextBox。这包括像 
  
      
        CategoryID
      
      和 
  
      
        SupplierID
      
      的字段,而它们是来自其它表的外键。 
 
    
    
    
      
   为了要求用户直接编辑外键,这个编辑界面还缺少了以下途径: 
 
    
    
      
        ·
        
        
      
      如果用户输入一个在数据库中不存在的 
  
      
        CategoryID
      
      或 
  
      
        SupplierID
      
      ,此更新将违反一个字段间的约束,引发一个异常。 
 
    
    
      
        ·
        
        
      
      这个编辑界面不包含任何数据验证。如果你不提供一个必填的值(例如 
  
      
        ProductName
      
      ),或者在要求输入数字的地方输入一个字符串,将抛出一个异常。未来的章节里将研究如何在编辑的用户界面中增加验证控件( 
  
      
        
          
            
              给编辑和新增界面增加验证控件
            
          
          )。
        
      
    
    
      
        ·
        
        
      
      通常,产品的所有非只读字段都必须包含在GridView里。如果我们从GridView里剔除一列,比如说 
  
      
        UnitPrice
      
      ,当更新数据时GridView将不会设置 
  
      
        UnitPrice
      
      
        UpdateParameters
      
      的值,这将把该数据库记录的 
  
      
        UnitPrice
      
      值更改为 
  
      
        NULL
      
      值。类似地,如果一个必填的字段,例如 
  
      
        ProductName
      
      ,从GridView中被剔除了,那么这个更新将失败,并出现一个上文中提及过的“ 
  
      
        Column 'ProductName' does not allow nulls
      
      ”异常。 
 
    
    
      
        ·
        
        
      
      这个编辑界面遗留了许多必要的格式化的问题。 
  
      
        UnitPrice
      
      显示为四位小数。理想地 
  
      
        CategoryID
      
      和 
  
      
        SupplierID
      
      应该包含下拉列表(DropDownList),它列出系统中存在的类别和供应商。 
 
    
    
      
   我们不得不承认现在这还有许多缺点,但这些将在未来的章节里谈及。 
 
    
    
      
        在DetailsView中插入、编辑和删除数据
      
    
    
      
   正如我们在之前的章节里看过的,DetailsView控件一次只显示一条记录,就像GridView一样,它也允许对当前显示的记录进行编辑和删除。不管是对最终用户来说从DetailsView进行编辑和删除的体验,还是在ASP.NET这一面的工作流程,都跟GridView是一样的。DetailsView和GridView不同的地方是,它还提供了内键的插入支持。 
 
    
    
      
   为了示范DetailsView的数据修改功能,首先,添加一个DetailsView控件到 
  
      
        Basics.aspx
      
      页面,放在现有的GridView的上方,并通过DetailsView的职能标记把它绑定到现有的ObjectDataSource。然后,清除DetailsView的 
  
      
        Height
      
      和 
  
      
        Width
      
      属性,并从它的职能标记中勾选“启用分页”。为了启用编辑、插入和删除支持,只需要简单地从它的职能标记里勾选上“启用插入”、“启用编辑”和“启用删除”。 
 
    
    
    
      
   与GridView一样,添加编辑、插入或删除支持会添加一个CommandField到该DetailsView,如下声明语法所示: 
 
    
    
      
        
          <asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataKeyNames="ProductID"
        
      
    
    
      
        
          
             DataSourceID="ObjectDataSource1" AllowPaging="True" EnableViewState="False">
          
        
      
    
    
      
        
          
             <Fields>
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False"
          
        
      
    
    
      
        
          
             ReadOnly="True" SortExpression="ProductID" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" />
          
        
      
    
    
      
        
          
             <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" ReadOnly="True"
          
        
      
    
    
      
        
          
             SortExpression="CategoryName" />
          
        
      
    
    
      
        
          
             <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" ReadOnly="True"
          
        
      
    
    
      
        
          
             SortExpression="SupplierName" />
          
        
      
    
    
      
        
          
             <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" ShowInsertButton="True" />
          
        
      
    
    
      
        
          
             </Fields>
          
        
      
    
    
      
        
          </asp:DetailsView>
        
      
    
    
      
   注意DetailsView的CommandField默认显示在列集合的下方。因为DetailsView的字段是以行的方式呈现的,所以CommandField也表现为包含插入、编辑和删除按钮的一行,出现在DetailsView的下方。 
 
    
    
    
      
   点击删除按钮就会开始与GridView相同的一系列的事件:一次回传;随之DetailsView基于 
  
      
        DataKeyNames
      
      的值组成它的ObjectDataSOurce的 
  
      
        DeleteParameters
      
      ;最后以调用ObjectDataSource的 
  
      
        Delete()
      
      方法结束,此方法从数据库中删除该产品的记录。在DetailsView中编辑也以GridView同样的方式运作。 
 
    
    
      
   为了插入数据,最终用户面对的是一个“新建”按钮,当点击时,DetailsView呈现为“插入模式”。在“插入模式”下,新建按钮被“插入”和“取消”按钮取代,并且显示那些 
  
      
        InsertVisible
      
      属性设置为 
  
      
        true
      
      (默认)的绑定列。这些自增长标识的数据字段,例如 
  
      
        ProductID
      
      字段,当通过职能标记绑定该DetailsView到数据源的时候让它们的 
  
      
        
          
            InsertVisible
          
        
      
      属性设置为 
  
      
        false
      
       。 
 
    
    
      
   当通过智能标记绑定数据源到DetailsView时,Visual Studio为自增长的字段设置其 
  
      
        InsertVisible
      
      属性为 
  
      
        false
      
      。只读的字段,像 
  
      
        CategoryName
      
      和 
  
      
        SupplierName
      
      ,将显示在“插入模式”下的用户界面中,除非它们的 
  
      
        InsertVisible
      
      属性也明确地设置为 
  
      
        false
      
       。稍稍花些时间把这两个字段的 
  
      
        InsertVisible
      
      属性设置为 
  
      
        false
      
       ,通过DetailsView的声明语法或者通过智能标记中的“编辑字段”链接。 
  
      
    
    
    
      
   在设置好 
  
      
        InsertVisible
      
      属性后,通过浏览器看看这个 
  
      
        Basics.aspx
      
      页面并点击新建按钮。图20显示的是添加一个新的饮料“Acme Tea”到我们的生产线时的DetailsView。 
 
    
    
    
      
   输入Acme Tea的详细信息并点击插入按钮后,随之发生一次回传并将这个新记录添加到 
  
      
        Products
      
      数据表。因为这个DetailsView是按照数据库中的顺序依次列出产品,所以我们必须翻到最后一页才能看到这个新增加的产品。 
 
    
    
    
      
        注意:
      
       DetailsView的 
  
      
        
          
            CurrentMode
          
        
      
       属性指示当前显示的界面并可以被设置为下面几个值之一: 
  
      
        Edit
      
      、 
  
      
        Insert
      
      或 
  
      
        ReadOnly
      
      。 
  
      
        
          
            DefaultMode
          
        
      
      属性则指示DetailsView在完成一次编辑或插入之后显示的模式,这在需要让DetailsView保持编辑或插入模式不变时是很有用的。 
 
    
    
      
   DetailsView这个点击插入和编辑的功能跟GridView有相同的局限性:用户必须通过文本框输入存在的 
  
      
        CategoryID
      
      和 
  
      
        SupplierID
      
      值;界面缺少任何验证的逻辑;产品的所有不允许为 
  
      
        NULL
      
      值或者没有在数据库中指定默认值的字段必须包含在插入界面里,等等。 
 
    
    
      
   在以后的章节里我们将会研究的扩展和提高GridView的编辑界面的技巧,同样可以应用到DetailsView的编辑和插入界面。 
 
    
    
      
        使用FormView做一个更灵活的数据修改用户界面
      
    
    
      
   FormView控件提供内建的对插入、编辑和删除数据的支持,不过因为它使用模版而不是列,它没有地方让我们添加像GridView和DetailsView控件提供给数据修改界面的绑定列和CommandField。取而代之的是,这个界面 – 收集新增一项或编辑现有项时用来收集用户输入的Web服务器控件,连同新增、编辑、删除、插入、保存和取消按钮 – 都必须手工添加到适当的模版里。幸运的是,Visual Studio将在通过它的职能标记的下拉列表绑定FormView到数据源时自动地创建需要的界面。 
 
    
    
      
   为了阐明这些技巧,首先,添加一个FormView控件到 
  
      
        Basics.aspx
      
      页面,并从FormView的职能标记,绑定它到已经存在的ObjectDataSource。这将为FormView生成一个 
  
      
        EditItemTemplate
      
      、 
  
      
        InsertItemTemplate
      
      和 
  
      
        ItemTemplate 
      
      ,用TextBox服务器控件收集用户的输入并用Button服务器控件作为添加新增、编辑、删除、插入、保存和取消按钮。另外,FormView的 
  
      
        DataKeyNames
      
      属性被设置到ObjectDataSource所返回的对象的主键( 
  
      
        ProductID
      
      )。最后,在FormView的职能标记中勾选“起用分页”选项。 
 
    
    
      
   下面展示出FormView绑定到ObjectDataSource后它的 
  
      
        ItemTemplate
      
      声明标记。默认地,每一个除了布尔值以外的产品的字段都绑定到一个Label服务器控件的 
  
      
        Text
      
      属性,相应地布尔类型的字段( 
  
      
        Discontinued
      
      )绑定到一个不可更改的CheckBox服务器控件的 
  
      
        Checked
      
      属性。为了让新增、编辑和删除按钮点击时能够引发某个FormView行为,必要的工作是将它们的 
  
      
        CommandName
      
      属性的值分别设置为 
  
      
        New
      
      、 
  
      
        Edit
      
      和 
  
      
        Delete
      
      。 
 
    
    
      
        
          <asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True" EnableViewState="False">
        
      
    
    
      
        
          
             <EditItemTemplate>
          
        
      
    
    
      
        
          
             ...
          
        
      
    
    
      
        
          
             </EditItemTemplate>
          
        
      
    
    
      
        
          
             <InsertItemTemplate>
          
        
      
    
    
      
        
          
             ...
          
        
      
    
    
      
        
          
             </InsertItemTemplate>
          
        
      
    
    
      
        
          
             <ItemTemplate>
          
        
      
    
    
      
        
          
             ProductID:
          
        
      
    
    
      
        
          
             <asp:Label ID="ProductIDLabel" runat="server" Text='<%# Eval("ProductID") %>'></asp:Label><br />
          
        
      
    
    
      
        
          
             ProductName:
          
        
      
    
    
      
        
          
             <asp:Label ID="ProductNameLabel" runat="server" Text='<%# Bind("ProductName") %>'>
          
        
      
    
    
      
        
          
             </asp:Label><br />
          
        
      
    
    
      
        
          
             SupplierID:
          
        
      
    
    
      
        
          
             <asp:Label ID="SupplierIDLabel" runat="server" Text='<%# Bind("SupplierID") %>'>
          
        
      
    
    
      
        
          
             </asp:Label><br />
          
        
      
    
    
      
        
          
             CategoryID:
          
        
      
    
    
      
        
          
             <asp:Label ID="CategoryIDLabel" runat="server" Text='<%# Bind("CategoryID") %>'>
          
        
      
    
    
      
        
          
             </asp:Label><br />
          
        
      
    
    
      
        
          
             QuantityPerUnit:
          
        
      
    
    
      
        
          
             <asp:Label ID="QuantityPerUnitLabel" runat="server" Text='<%# Bind("QuantityPerUnit") %>'>
          
        
      
    
    
      
        
          
             </asp:Label><br />
          
        
      
    
    
      
        
          
             UnitPrice:
          
        
      
    
    
      
        
          
             <asp:Label ID="UnitPriceLabel" runat="server" Text='<%# Bind("UnitPrice") %>'></asp:Label><br />
          
        
      
    
    
      
        
          
             UnitsInStock:
          
        
      
    
    
      
        
          
             <asp:Label ID="UnitsInStockLabel" runat="server" Text='<%# Bind("UnitsInStock") %>'>
          
        
      
    
    
      
        
          
             </asp:Label><br />
          
        
      
    
    
      
        
          
             UnitsOnOrder:
          
        
      
    
    
      
        
          
             <asp:Label ID="UnitsOnOrderLabel" runat="server" Text='<%# Bind("UnitsOnOrder") %>'>
          
        
      
    
    
      
        
          
             </asp:Label><br />
          
        
      
    
    
      
        
          
             ReorderLevel:
          
        
      
    
    
      
        
          
             <asp:Label ID="ReorderLevelLabel" runat="server" Text='<%# Bind("ReorderLevel") %>'>
          
        
      
    
    
      
        
          
             </asp:Label><br />
          
        
      
    
    
      
        
          
             Discontinued:
          
        
      
    
    
      
        
          
             <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked='<%# Bind("Discontinued") %>'
          
        
      
    
    
      
        
          
             Enabled="false" /><br />
          
        
      
    
    
      
        
          
             CategoryName:
          
        
      
    
    
      
        
          
             <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Bind("CategoryName") %>'>
          
        
      
    
    
      
        
          
             </asp:Label><br />
          
        
      
    
    
      
        
          
             SupplierName:
          
        
      
    
    
      
        
          
             <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Bind("SupplierName") %>'>
          
        
      
    
    
      
        
          
             </asp:Label><br />
          
        
      
    
    
      
        
          
             <asp:Link 
          
        
      
    
    
      
      发表评论


        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
          
          
					
					
评论