ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

Rate It (1)

Last post 08-22-2008 9:14 AM by Caph. 14 replies.

Sort Posts:

  • ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    09-16-2006, 10:53 PM
    • Member
      252 point Member
    • jdcrutchley
    • Member since 04-27-2004, 8:34 PM
    • Posts 63

    DISCLAIMER:  I'm no expert on the GridView lifecycle, but empirical evidence seems to suggest this stuff is correct...  I'm sure if I've gotten anything wrong here an expert will jump in and correct me.

    Issue:

    When you hook up a GridView to an ObjectDataSource when the ViewState is disabled, there is a major concurrency issue.  The issue is that if Bob and Joe are both looking at the same list, and Joe deletes Row #3, then Bob goes to delete Row #5, Bob is really going to delete Row #6 (of the original list they were looking at) because all of the rows have shifted upward after Joe's delete.  This could happen even with the ObjectDataSource's concurrency stuff running because things like Delete are just looking at row number.  Note that this might also happen with ViewState enabled - I'm not really sure because I never enable ViewState for big honking GridViews...

    Root Cause:

    When the GridView fires off a Delete event, it sets the form's __EVENTTARGET variable to the ID of the GridView (like ctl00$ContentPlaceHolder1$GridView1) and the __EVENTARGUMENT to "Delete$x" where the "x" part represents the row of the deleted item.  So, Joe fires off his delete of Row #3 and __EVENTARGUMENT is "Delete$3", and this tells the ObjectDataSource "Delete the thing at Row #3", so it grabs the DataKey and does a delete.  Now Bob fires off his delete before refreshing the display and __EVENTARGUMENT gets set to "Delete$5".  Trouble is, now what was Row #5 is really Row #4 because of Joe's previous delete, so the ObjectDataSource ends up deleting the wrong line.  Another side effect of this is that hitting the browser's refresh button immediately after doing a delete of Row #3 will cause whatever happens to now be on Row #3 to be deleted without error.  Again, this might not be the same if you have concurrency set on the ObjectDataSource and ViewState enabled on your GridView - but who wants all of the data in a huge GridView getting sent to the client anyway?

    A Possible Solution:

    Well, the only way I've found to get around this is to just chuck the ObjectDataSource and go back to manually binding my data.  (That's probably a tiny bit faster anyway since the calls can be early-bound.)  Once that's done, you can hook up to the GridView's OnRowCreated event and do something like this:

    if (e.Row.RowType == DataControlRowType.DataRow)
        e.Row.ID = "ctl" + ((MyNamespace.MyClass)e.Row.DataItem).ID;
    

    Doing this will make the ID of the control represent the unique ID of your object instead of the row number. That way command event gets fired if that unique item still exists in the list, but if it doesn't, the command event is never fired.  Also, if you want to let the user know that their action didn't happen because of a concurrency issue, you can check in OnLoad to see if you're in a postback and if FindControl can't find a control with the ID in  __EVENTTARGET then the item they were going to perform the action on is gone and you can give them a notification.

    This method doesn't work at all with the DataSource controls because the event that gets fired for them always uses the GridView as the sender (as opposed to the LinkButton within it) and so there's no way to use this hackery to change from row number to object ID.  I tried just changing the CommandArgument in the OnDeleting event, but it's readonly and that would confuse the DataSource anyway because it's expecting a row number...

    Drawbacks:

    Well, one obvious drawback is you can't use the ObjectDataSource to automagically update your data.  To me that's a very small issue though since writing a "GV1.DataSource=something; GV1.DataBind();" isn't the hardest thing in the world and I don't like alot of automagical stuff anyway...  There are probably other ones that will pop up as I use this more, but for now it's working great for me.

    As always, there's more than one way to beat a mule, so you could also just use HyperLinks for your actions and add your object ID to the querystring if you don't mind adding junk to your querystring.  That'd work too, but again, you couldn't use the ObjectDataSource to automagically delete your data.

    Bottom Line:

    ObjectDataSource is good for quick prototyping or single-user applications, but not so good for real-world applications that will have multiple users updating the same data.

    jdc

    -- jdc
  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    11-14-2006, 11:07 AM
    • Contributor
      4,100 point Contributor
    • Scott Mitchell
    • Member since 06-15-2002, 8:41 PM
    • San Diego, CA
    • Posts 707
    • ASPInsiders
      TrustedFriends-MVPs

    I also wanted to share my approach [see http://scottonwriting.net/sowblog/posts/10054.aspx for additional comments]:

    In the Editing, Inserting, and Deleting Data tutorials of my Working with Data in ASP.NET 2.0 tutorial series, I demonstrate how the GridView, DetailsView, and FormView can all support the built-in editing, deleting, and inserting capabilities with view state disabled. The reason is because the key data Web control properties (such as the DataKeys collection), are stored in control state, which is always persisted to view state regardless of the control's EnableViewState property value.

    In my tutorials I don't mention the view state issue, I just silently set the EnableViewState property to false. And this works wonderfully when testing the page. Deletes and edits work exactly as expected, with or without view state enabled. However, a rather insidious behavior can rear its head when there are multiple users visiting the same page. This potential trap was pointed out to me by alert reader Jamie Crutchley, whose observed and shared information about this problem on the ASP.NET Forums in the past. I'll explain the problem (and a potential workaround) in my own words, but you can read Jamie's posts here and here, if you'd rather.

    PROBLEM: Two users - Alice and Bob - visit a page (Products.aspx) that uses a GridView whose to list products; the GridView's view state has been disabled. The GridView uses its DataKeys collection to store the primary key values of the three products. Imagine that the products listed are Pens, Books, and Paper, and their respective primary key values are 1, 2, and 3.

    1. Alice clicks on the Delete button for the first product in the grid (Pens).
    2. A postback occurs. Because the GridView's view state has been disabled, on postback the data is re-read from the GridView's data source. This has the side effect of repopulating the DataKeys collection with the newly read data!
    3. Since the first row index was clicked, the GridView grabs the DataKeys value indexed at 0 and issues a delete based on that primary key value (1).
    4. Bob still sees all three products on the page (since he loaded the page before Alice deleted Pens). Sometime after Alice has made her deletion, Bob, too, decides that Pens must be deleted. He clicks on the Delete button for Pens.
    5. A postback occurs. Because the GridView's view state has been disabled, on postback the data is re-read from the GridView's data source. This has the side effect of repopulating the DataKeys collection with the newly read data!
    6. Since the first row index was clicked, the GridView grabs the DataKeys value indexed at 0. However, since the DataKeys collection has been reloaded in Step 5, the first DataKeys value is the primary key of Books (since Pens has since been deleted). The consequence is that Books is deleted, even though Bob wanted to delete Pens!!

    More generally, if Alice deletes any product whose index preceeds the index of the record Bob deletes, Bob's delete will actually delete a different record. Similarly, if Alice deletes a preceeding record of the one Bob is editing, the edits will be applied to a preceeding row. Eep.

    SHORT AND SIMPLE SOLUTION: Unless you are absolutely, 100%, certifiably, unconditionally certain that there will never, ever, not in a million years be two users concurrently editing/deleting records, then be sure to leave the GridView / DetailsView / FormView's EnableViewState property to True (the default).

    MORE INVOLVED SOLUTION: If you really would like to reduce the page size by disabling view state, you can use the following “hack”... When a Delete (or Edit) button is clicked in the GridView the RowCommand event fires before the DataKeys collection is internally repopulated. Therefore, you can create an event handler that includes code that “saves” the DataKeys value(s) for the record being deleted. Then, in the ObjectDataSource's Deleting event handler you can reassign this value back to the primary key parameter(s).

    Here's some code to implement this approach. First, in the RowCommand event handler the primary key (ProductID) is saved in a page-level variable:

        1 int recordToDelete = -1;

        2 protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)

        3 {

        4     recordToDelete = Convert.ToInt32(GridView1.DataKeys[Convert.ToInt32(e.CommandArgument)].Value);

        5 }

    Then, in the ObjectDataSource's Deleting event, assign the page-level variable to the appropriate parameter:

        1 protected void ObjectDataSource1_Deleting(object sender, ObjectDataSourceMethodEventArgs e)

        2 {

        3     if (recordToDelete > 0)

        4         e.InputParameters["ProductID"] = recordToDelete;

        5 }

     

    The tutorials and code at Working with Data in ASP.NET 2.0 will be updated so that they no longer disable view state and will include a similar warning as to this blog entry, although due to the breadth of material online, it may take several days or weeks to get all the changes made and propagated...

     http://scottonwriting.net/sowblog/posts/10054.aspx

    Happy Programming!


    -- Scott Mitchell
    -- mitchell@4guysfromrolla.com
    -- http://scottonwriting.net/sowblog/
    -- http://www.4GuysFromRolla.com/ScottMitchell.shtml
  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    12-05-2006, 2:25 PM
    • Contributor
      2,750 point Contributor
    • RichardD
    • Member since 09-03-2002, 11:43 AM
    • Sussex, UK
    • Posts 361
    This workaround doesn't seem to work for a GridView bound to an ObjectDataSource control:
    • The CommandArgument is not set for the RowCommand event.
    • By the time the RowCommand event is raised, the DataKeys collection has already been updated.

    The only solution I've found is to create a derived control:
    • Override the OnLoad LoadControlState method and take a copy of the DataKeys;
    • Override the OnBubbleEvent, cast the source parameter to a GridViewRow, and use the RowIndex to find the key from the copied list;
    • Finally, override the RowDeleting and RowUpdating methods, and copy the values from the original key to the event arguments.

    1    using System;
    2 using System.Collections;
    3 using System.Web;
    4 using System.Web.UI;
    5 using System.Web.UI.WebControls;
    6
    7 public class ExtendedGridView : GridView
    8 {
    9 private DataKey[] _originalDataKeys;
    10 private DataKey _rowCommandDataKey;
    11
    12 public ExtendedGridView()
    13 {
    14 }
    15
    16 protected override void LoadControlState(object savedState)
    17 {
    18 base.LoadControlState(savedState);
    19
    20 if (!EnableViewState)
    21 {
    22 DataKeyArray keys = this.DataKeys;
    23 if (null != keys && 0 != keys.Count)
    24 {
    25 _originalDataKeys = new DataKey[keys.Count];
    26 keys.CopyTo(_originalDataKeys, 0);
    27 }
    28 }
    29 }
    30
    31 protected override bool OnBubbleEvent(object source, EventArgs e)
    32 {
    33 if (null != _originalDataKeys)
    34 {
    35 GridViewRow row = source as GridViewRow;
    36 GridViewCommandEventArgs args = e as GridViewCommandEventArgs;
    37 if (null != row && null != args)
    38 {
    39 _rowCommandDataKey = _originalDataKeys[row.RowIndex];
    40 }
    41 }
    42
    43 return base.OnBubbleEvent(source, e);
    44 }
    45
    46 protected override void OnRowDeleting(GridViewDeleteEventArgs e)
    47 {
    48 if (null != _rowCommandDataKey)
    49 {
    50 foreach (DictionaryEntry entry in _rowCommandDataKey.Values)
    51 {
    52 e.Keys[entry.Key] = entry.Value;
    53 }
    54
    55 _rowCommandDataKey = null;
    56 _originalDataKeys = null;
    57 }
    58
    59 base.OnRowDeleting(e);
    60 }
    61
    62 protected override void OnRowUpdating(GridViewUpdateEventArgs e)
    63 {
    64 if (null != _rowCommandDataKey)
    65 {
    66 foreach (DictionaryEntry entry in _rowCommandDataKey.Values)
    67 {
    68 e.Keys[entry.Key] = entry.Value;
    69 }
    70
    71 _rowCommandDataKey = null;
    72 _originalDataKeys = null;
    73 }
    74
    75 base.OnRowUpdating(e);
    76 }
    77 }
     
  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    12-05-2006, 2:47 PM
    • Contributor
      2,750 point Contributor
    • RichardD
    • Member since 09-03-2002, 11:43 AM
    • Sussex, UK
    • Posts 361
  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    12-05-2006, 11:43 PM
    • Contributor
      4,100 point Contributor
    • Scott Mitchell
    • Member since 06-15-2002, 8:41 PM
    • San Diego, CA
    • Posts 707
    • ASPInsiders
      TrustedFriends-MVPs

    My workaround works fine in my tests using an ObjectDataSource bound to the GridView. The following works for me:

      

    1    <%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="Basics.aspx.cs" Inherits="EditInsertDelete_Basics" Title="Untitled Page" %>
    2    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">
    3        <h2>
    4            The Basics of Editing, Inserting, and Deleting</h2>
    5            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="DeleteProduct"
    6                InsertMethod="AddProduct" SelectMethod="GetProducts"
    7                TypeName="ProductsBLL" UpdateMethod="UpdateProduct" OnDataBinding="ObjectDataSource1_DataBinding" OnDeleting="ObjectDataSource1_Deleting" OnSelecting="ObjectDataSource1_Selecting">
    8                <DeleteParameters>
    9                    <asp:Parameter Name="productID" Type="Int32" />
    10               </DeleteParameters>
    11               <UpdateParameters>
    12                   <asp:Parameter Name="productName" Type="String" />
    13                   <asp:Parameter Name="supplierID" Type="Int32" />
    14                   <asp:Parameter Name="categoryID" Type="Int32" />
    15                   <asp:Parameter Name="quantityPerUnit" Type="String" />
    16                   <asp:Parameter Name="unitPrice" Type="Decimal" />
    17                   <asp:Parameter Name="unitsInStock" Type="Int16" />
    18                   <asp:Parameter Name="unitsOnOrder" Type="Int16" />
    19                   <asp:Parameter Name="reorderLevel" Type="Int16" />
    20                   <asp:Parameter Name="discontinued" Type="Boolean" />
    21                   <asp:Parameter Name="productID" Type="Int32" />
    22               </UpdateParameters>
    23               <InsertParameters>
    24                   <asp:Parameter Name="productName" Type="String" />
    25                   <asp:Parameter Name="supplierID" Type="Int32" />
    26                   <asp:Parameter Name="categoryID" Type="Int32" />
    27                   <asp:Parameter Name="quantityPerUnit" Type="String" />
    28                   <asp:Parameter Name="unitPrice" Type="Decimal" />
    29                   <asp:Parameter Name="unitsInStock" Type="Int16" />
    30                   <asp:Parameter Name="unitsOnOrder" Type="Int16" />
    31                   <asp:Parameter Name="reorderLevel" Type="Int16" />
    32                   <asp:Parameter Name="discontinued" Type="Boolean" />
    33               </InsertParameters>
    34           </asp:ObjectDataSource>
    35       <h3>Editing, Inserting, and Deleting Data from a FormView</h3>
    36       <p>
    37           <asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True" EnableViewState="False">
    38               <EditItemTemplate>
    39                   ProductID:
    40                   <asp:Label ID="ProductIDLabel1" runat="server" Text='<%# Eval("ProductID") %>'></asp:Label><br />
    41                   ProductName:
    42                   <asp:TextBox ID="ProductNameTextBox" runat="server" Text='<%# Bind("ProductName") %>'>
    43                   </asp:TextBox><br />
    44                   SupplierID:
    45                   <asp:TextBox ID="SupplierIDTextBox" runat="server" Text='<%# Bind("SupplierID") %>'>
    46                   </asp:TextBox><br />
    47                   CategoryID:
    48                   <asp:TextBox ID="CategoryIDTextBox" runat="server" Text='<%# Bind("CategoryID") %>'>
    49                   </asp:TextBox><br />
    50                   QuantityPerUnit:
    51                   <asp:TextBox ID="QuantityPerUnitTextBox" runat="server" Text='<%# Bind("QuantityPerUnit") %>'>
    52                   </asp:TextBox><br />
    53                   UnitPrice:
    54                   <asp:TextBox ID="UnitPriceTextBox" runat="server" Text='<%# Bind("UnitPrice") %>'>
    55                   </asp:TextBox><br />
    56                   UnitsInStock:
    57                   <asp:TextBox ID="UnitsInStockTextBox" runat="server" Text='<%# Bind("UnitsInStock") %>'>
    58                   </asp:TextBox><br />
    59                   UnitsOnOrder:
    60                   <asp:TextBox ID="UnitsOnOrderTextBox" runat="server" Text='<%# Bind("UnitsOnOrder") %>'>
    61                   </asp:TextBox><br />
    62                   ReorderLevel:
    63                   <asp:TextBox ID="ReorderLevelTextBox" runat="server" Text='<%# Bind("ReorderLevel") %>'>
    64                   </asp:TextBox><br />
    65                   Discontinued:
    66                   <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked='<%# Bind("Discontinued") %>' />
    67                   <br />
    68                   <asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True" CommandName="Update"
    69                       Text="Update">
    70                   </asp:LinkButton>
    71                   <asp:LinkButton ID="UpdateCancelButton" runat="server" CausesValidation="False" CommandName="Cancel"
    72                       Text="Cancel">
    73                   </asp:LinkButton>
    74               </EditItemTemplate>
    75               <InsertItemTemplate>
    76                   ProductName:
    77                   <asp:TextBox ID="ProductNameTextBox" runat="server" Text='<%# Bind("ProductName") %>'>
    78                   </asp:TextBox><br />
    79                   SupplierID:
    80                   <asp:TextBox ID="SupplierIDTextBox" runat="server" Text='<%# Bind("SupplierID") %>'>
    81                   </asp:TextBox><br />
    82                   CategoryID:
    83                   <asp:TextBox ID="CategoryIDTextBox" runat="server" Text='<%# Bind("CategoryID") %>'>
    84                   </asp:TextBox><br />
    85                   QuantityPerUnit:
    86                   <asp:TextBox ID="QuantityPerUnitTextBox" runat="server" Text='<%# Bind("QuantityPerUnit") %>'>
    87                   </asp:TextBox><br />
    88                   UnitPrice:
    89                   <asp:TextBox ID="UnitPriceTextBox" runat="server" Text='<%# Bind("UnitPrice") %>'>
    90                   </asp:TextBox><br />
    91                   UnitsInStock:
    92                   <asp:TextBox ID="UnitsInStockTextBox" runat="server" Text='<%# Bind("UnitsInStock") %>'>
    93                   </asp:TextBox><br />
    94                   UnitsOnOrder:
    95                   <asp:TextBox ID="UnitsOnOrderTextBox" runat="server" Text='<%# Bind("UnitsOnOrder") %>'>
    96                   </asp:TextBox><br />
    97                   ReorderLevel:
    98                   <asp:TextBox ID="ReorderLevelTextBox" runat="server" Text='<%# Bind("ReorderLevel") %>'>
    99                   </asp:TextBox><br />
    100                  Discontinued:
    101                  <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked='<%# Bind("Discontinued") %>' /><br />
    102                  <asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" CommandName="Insert"
    103                      Text="Insert">
    104                  </asp:LinkButton>
    105                  <asp:LinkButton ID="InsertCancelButton" runat="server" CausesValidation="False" CommandName="Cancel"
    106                      Text="Cancel">
    107                  </asp:LinkButton>
    108              </InsertItemTemplate>
    109              <ItemTemplate>
    110                  ProductID:
    111                  <asp:Label ID="ProductIDLabel" runat="server" Text='<%# Eval("ProductID") %>'></asp:Label><br />
    112                  ProductName:
    113                  <asp:Label ID="ProductNameLabel" runat="server" Text='<%# Bind("ProductName") %>'>
    114                  </asp:Label><br />
    115                  SupplierID:
    116                  <asp:Label ID="SupplierIDLabel" runat="server" Text='<%# Bind("SupplierID") %>'>
    117                  </asp:Label><br />
    118                  CategoryID:
    119                  <asp:Label ID="CategoryIDLabel" runat="server" Text='<%# Bind("CategoryID") %>'>
    120                  </asp:Label><br />
    121                  QuantityPerUnit:
    122                  <asp:Label ID="QuantityPerUnitLabel" runat="server" Text='<%# Bind("QuantityPerUnit") %>'>
    123                  </asp:Label><br />
    124                  UnitPrice:
    125                  <asp:Label ID="UnitPriceLabel" runat="server" Text='<%# Bind("UnitPrice") %>'></asp:Label><br />
    126                  UnitsInStock:
    127                  <asp:Label ID="UnitsInStockLabel" runat="server" Text='<%# Bind("UnitsInStock") %>'>
    128                  </asp:Label><br />
    129                  UnitsOnOrder:
    130                  <asp:Label ID="UnitsOnOrderLabel" runat="server" Text='<%# Bind("UnitsOnOrder") %>'>
    131                  </asp:Label><br />
    132                  ReorderLevel:
    133                  <asp:Label ID="ReorderLevelLabel" runat="server" Text='<%# Bind("ReorderLevel") %>'>
    134                  </asp:Label><br />
    135                  Discontinued:
    136                  <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked='<%# Bind("Discontinued") %>'
    137                      Enabled="false" /><br />
    138                  CategoryName:
    139                  <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Bind("CategoryName") %>'>
    140                  </asp:Label><br />
    141                  SupplierName:
    142                  <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Bind("SupplierName") %>'>
    143                  </asp:Label><br />
    144                  <asp:LinkButton ID="EditButton" runat="server" CausesValidation="False" CommandName="Edit"
    145                      Text="Edit">
    146                  </asp:LinkButton>
    147                  <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete"
    148                      Text="Delete">
    149                  </asp:LinkButton>
    150                  <asp:LinkButton ID="NewButton" runat="server" CausesValidation="False" CommandName="New"
    151                      Text="New">
    152                  </asp:LinkButton>
    153              </ItemTemplate>
    154          </asp:FormView>
    155           </p>
    156      <h3>
    157          Editing, Inserting, and Deleting Data from a DetailsView</h3>
    158      <p>
    159          <asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataKeyNames="ProductID"
    160              DataSourceID="ObjectDataSource1" AllowPaging="True" EnableViewState="False">
    161              <Fields>
    162                  <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False"
    163                      ReadOnly="True" SortExpression="ProductID" />
    164                  <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
    165                  <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" />
    166                  <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" />
    167                  <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" />
    168                  <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" />
    169                  <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" />
    170                  <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" />
    171                  <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" />
    172                  <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" />
    173                  <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" ReadOnly="True"
    174                      SortExpression="CategoryName" InsertVisible="False" />
    175                  <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" ReadOnly="True"
    176                      SortExpression="SupplierName" InsertVisible="False" />
    177                  <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" ShowInsertButton="True" />
    178              </Fields>
    179          </asp:DetailsView>
    180           </p>
    181      <h3>
    182          Editing and Deleting Data from a GridView</h3>
    183      <p>
    184          <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False" OnRowCommand="GridView1_RowCommand">
    185              <Columns>
    186                  <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
    187                  <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False"
    188                      ReadOnly="True" SortExpression="ProductID" />
    189                  <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
    190                  <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" />
    191                  <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" />
    192                  <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" />
    193                  <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" />
    194                  <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" />
    195                  <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" />
    196                  <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" />
    197                  <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" />
    198                  <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" ReadOnly="True"
    199                      SortExpression="CategoryName" />
    200                  <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" ReadOnly="True"
    201                      SortExpression="SupplierName" />
    202              </Columns>
    203          </asp:GridView>
    204            </p>
    205  </asp:Content>
    

     

     

     

    1    using System;
    2    using System.Data;
    3    using System.Configuration;
    4    using System.Collections;
    5    using System.Web;
    6    using System.Web.Security;
    7    using System.Web.UI;
    8    using System.Web.UI.WebControls;
    9    using System.Web.UI.WebControls.WebParts;
    10   using System.Web.UI.HtmlControls;
    11   
    12   public partial class EditInsertDelete_Basics : System.Web.UI.Page
    13   {
    14       
    15   
    16       protected void Page_Load(object sender, EventArgs e)
    17       {
    18           int y = 0;
    19       }
    20       protected void ObjectDataSource1_Deleting(object sender, ObjectDataSourceMethodEventArgs e)
    21       {
    22           if (recordToDelete > 0)
    23               e.InputParameters["ProductID"] = recordToDelete;
    24       }
    25       protected void ObjectDataSource1_DataBinding(object sender, EventArgs e)
    26       {
    27           int x = 0;
    28       }
    29       protected void ObjectDataSource1_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
    30       {
    31           int x = 0;
    32       }
    33   
    34       int recordToDelete = -1;
    35       protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
    36       {
    37           recordToDelete = Convert.ToInt32(GridView1.DataKeys[Convert.ToInt32(e.CommandArgument)].Value);
    38           int x = 0;
    39       }
    40   }
    41   
    
      
    Happy Programming!


    -- Scott Mitchell
    -- mitchell@4guysfromrolla.com
    -- http://scottonwriting.net/sowblog/
    -- http://www.4GuysFromRolla.com/ScottMitchell.shtml
  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    12-06-2006, 6:23 AM
    • Contributor
      2,750 point Contributor
    • RichardD
    • Member since 09-03-2002, 11:43 AM
    • Sussex, UK
    • Posts 361

    The workaround works fine if you're using a CommandField, but not if you're using a TemplateField.

    • With a CommandField, the generated postback script looks like:
      __doPostBack('GridView1','Delete$2')
    • With a TemplateField, the script looks like:
      __doPostBack('grdData$ctl04$ctl00','')

    Using a TemplateField, the child controls have to be re-created before the event can be raised, which involves re-binding the grid to the data source. As a result, by the time the RowCommand event is raised, the DataKeys collection has been re-populated.

  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    12-06-2006, 9:27 AM
    • Member
      252 point Member
    • jdcrutchley
    • Member since 04-27-2004, 8:34 PM
    • Posts 63

    Unfortunately I've lost the code, but I experimented once with creating a derived button control that would emit a "doPostBack" script targetted to it's parent when placed in a templated control.  So instead of doing __doPostBack('grdData$ctl04$ctl00','') it would do __doPostBack('grdData','Delete$2').  That way the event could be raised as if it came from the gridview before instantiating the templated controls.  I wish I still had the code so I could show it to you, but essentially I just overrode the Render method.  There's probably a better way to do it, but I was just playing around with alternatives at the time.

     

    -- jdc
  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    12-06-2006, 10:36 AM
    • Contributor
      2,750 point Contributor
    • RichardD
    • Member since 09-03-2002, 11:43 AM
    • Sussex, UK
    • Posts 361

    OK, this version of the code should work for the Select, Edit, Update and Delete commands, whether you're using a CommandField or a TemplateField:

    1    using System;
    2    using System.Collections;
    3    using System.Collections.Generic;
    4    using System.ComponentModel;
    5    using System.Globalization;
    6    using System.Security.Permissions;
    7    using System.Text;
    8    using System.Web;
    9    using System.Web.UI;
    10   using System.Web.UI.WebControls;
    11   
    12   [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    13   [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    14   public class ExtendedGridView : GridView
    15   {
    16       private DataKey[] _originalDataKeys;
    17       private DataKey _rowCommandDataKey;
    18       private int? _selectedIndex;
    19       private int? _editIndex;
    20       private bool _suppressDataBind;
    21   
    22       protected override void LoadControlState(object savedState)
    23       {
    24           base.LoadControlState(savedState);
    25   
    26           if (!EnableViewState)
    27           {
    28               DataKeyArray keys = this.DataKeys;
    29               if (null != keys && 0 != keys.Count)
    30               {
    31                   _originalDataKeys = new DataKey[keys.Count];
    32                   keys.CopyTo(_originalDataKeys, 0);
    33               }
    34           }
    35       }
    36   
    37       protected override bool OnBubbleEvent(object source, EventArgs e)
    38       {
    39           if (null != _originalDataKeys && null == _rowCommandDataKey)
    40           {
    41               GridViewRow row = source as GridViewRow;
    42               GridViewCommandEventArgs args = e as GridViewCommandEventArgs;
    43               if (null != row && null != args && 0 <= row.RowIndex && row.RowIndex < _originalDataKeys.Length)
    44               {
    45                   _rowCommandDataKey = _originalDataKeys[row.RowIndex];
    46               }
    47           }
    48   
    49           return base.OnBubbleEvent(source, e);
    50       }
    51   
    52       protected override void OnRowCommand(GridViewCommandEventArgs e)
    53       {
    54           if (null != _originalDataKeys && null == _rowCommandDataKey)
    55           {
    56               string arg = Convert.ToString(e.CommandArgument, CultureInfo.InvariantCulture);
    57               if (!string.IsNullOrEmpty(arg))
    58               {
    59                   int rowIndex;
    60                   if (int.TryParse(arg, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out rowIndex))
    61                   {
    62                       if (0 <= rowIndex && rowIndex < _originalDataKeys.Length)
    63                       {
    64                           _rowCommandDataKey = _originalDataKeys[rowIndex];
    65                       }
    66                   }
    67               }
    68           }
    69   
    70           base.OnRowCommand(e);
    71       }
    72   
    73       protected override void OnRowDeleting(GridViewDeleteEventArgs e)
    74       {
    75           if (null != _rowCommandDataKey)
    76           {
    77               foreach (DictionaryEntry entry in _rowCommandDataKey.Values)
    78               {
    79                   e.Keys[entry.Key] = entry.Value;
    80               }
    81   
    82               _rowCommandDataKey = null;
    83               _originalDataKeys = null;
    84           }
    85   
    86           base.OnRowDeleting(e);
    87       }
    88   
    89       protected override void OnRowUpdating(GridViewUpdateEventArgs e)
    90       {
    91           if (null != _rowCommandDataKey)
    92           {
    93               foreach (DictionaryEntry entry in _rowCommandDataKey.Values)
    94               {
    95                   e.Keys[entry.Key] = entry.Value;
    96               }
    97   
    98               _rowCommandDataKey = null;
    99               _originalDataKeys = null;
    100          }
    101  
    102          base.OnRowUpdating(e);
    103      }
    104  
    105      protected override void OnDataSourceViewChanged(object sender, EventArgs e)
    106      {
    107          _originalDataKeys = null;
    108          _rowCommandDataKey = null;
    109          base.OnDataSourceViewChanged(sender, e);
    110      }
    111      
    112      protected override void EnsureDataBound()
    113      {
    114          if (!_suppressDataBind)
    115          {
    116              base.EnsureDataBound();
    117          }
    118      }
    119  
    120      protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
    121      {
    122          _selectedIndex = null;
    123          _editIndex = null;
    124  
    125          int result = base.CreateChildControls(dataSource, dataBinding);
    126  
    127          if (base.RequiresDataBinding)
    128          {
    129              base.SelectedIndex = _selectedIndex ?? -1;
    130              base.EditIndex = _editIndex ?? -1;
    131          }
    132          else
    133          {
    134              _suppressDataBind = true;
    135              try
    136              {
    137                  base.SelectedIndex = _selectedIndex ?? -1;
    138                  base.EditIndex = _editIndex ?? -1;
    139                  base.RequiresDataBinding = false;
    140              }
    141              finally
    142              {
    143                  _suppressDataBind = false;
    144              }
    145          }
    146  
    147          return result;
    148      }
    149  
    150      protected override GridViewRow CreateRow(int rowIndex, int dataSourceIndex, DataControlRowType rowType, DataControlRowState rowState)
    151      {
    152          if (DataControlRowType.DataRow == rowType && null != _originalDataKeys)
    153          {
    154              int index = base.SelectedIndex;
    155              if (-1 != index && index < _originalDataKeys.Length)
    156              {
    157                  if (DataKeyEqualityComparer.Instance.Equals(_originalDataKeys[index], this.DataKeys[rowIndex]))
    158                  {
    159                      _selectedIndex = rowIndex;
    160                      rowState |= DataControlRowState.Selected;
    161                  }
    162                  else
    163                  {
    164                      rowState &= ~DataControlRowState.Selected;
    165                  }
    166              }
    167  
    168              index = base.EditIndex;
    169              if (-1 != index && index < _originalDataKeys.Length)
    170              {
    171                  if (DataKeyEqualityComparer.Instance.Equals(_originalDataKeys[index], this.DataKeys[rowIndex]))
    172                  {
    173                      _editIndex = rowIndex;
    174                      rowState |= DataControlRowState.Edit;
    175                  }
    176                  else if (DataControlRowState.Edit == (DataControlRowState.Edit & rowState))
    177                  {
    178                      rowState &= ~DataControlRowState.Edit;
    179                  }
    180              }
    181          }
    182  
    183          return base.CreateRow(rowIndex, dataSourceIndex, rowType, rowState);
    184      }
    185      
    186      protected static int FindKeyIndex(DataKeyArray keys, DataKey value)
    187      {
    188          if (null == keys || 0 == keys.Count) return -1;
    189          if (null == value) return -1;
    190          
    191          IEqualityComparer comparer = DataKeyEqualityComparer.Instance;
    192          for (int index = 0; index < keys.Count; index++)
    193          {
    194              if (comparer.Equals(keys[index], value))
    195                  return index;
    196          }
    197          
    198          return -1;
    199      }    
    200  }
    201  
    202  [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    203  public sealed class DataKeyEqualityComparer : IEqualityComparer
    204  {
    205      private static readonly DataKeyEqualityComparer _instance = new DataKeyEqualityComparer();
    206  
    207      private DataKeyEqualityComparer()
    208      {
    209      }
    210  
    211      public static DataKeyEqualityComparer Instance
    212      {
    213          get { return _instance; }
    214      }
    215  
    216      int IEqualityComparer.GetHashCode(DataKey obj)
    217      {
    218          if (null == obj) return 0;
    219          return obj.GetHashCode();
    220      }
    221  
    222      public bool Equals(DataKey x, DataKey y)
    223      {
    224          if (object.ReferenceEquals(x, y)) return true;
    225          if (null == x || null == y) return false;
    226          return Equals(x.Values, y.Values);
    227      }
    228  
    229      private static bool Equals(IDictionary x, IDictionary y)
    230      {
    231          if (object.ReferenceEquals(x, y)) return true;
    232          if (null == x || null == y) return false;
    233          if (x.Count != y.Count) return false;
    234  
    235          foreach (DictionaryEntry entry in x)
    236          {
    237              if (!y.Contains(entry.Key)) return false;
    238              if (!ValueEquals(entry.Value, y[entry.Key])) return false;
    239          }
    240  
    241          return true;
    242      }
    243  
    244      private static bool ValueEquals(object x, object y)
    245      {
    246          if (object.ReferenceEquals(null, x)) return object.ReferenceEquals(null, y);
    247          if (object.ReferenceEquals(null, y)) return false;
    248          return x.Equals(y);
    249      }
    250  }
    
     
  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    12-06-2006, 1:36 PM
    • Member
      17 point Member
    • klabran
    • Member since 11-29-2006, 9:12 PM
    • Posts 6

    I confirmed Scott's setup with the objectdatasource...

    However, is there a work around for sqldatasources?

    I tried adapting your code (Scott) to them and found that I couldn't capture the value before postback occurs to repop the datakeys.

  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    12-06-2006, 2:18 PM
    • Contributor
      4,100 point Contributor
    • Scott Mitchell
    • Member since 06-15-2002, 8:41 PM
    • San Diego, CA
    • Posts 707
    • ASPInsiders
      TrustedFriends-MVPs
    jdcrutchley:

    Unfortunately I've lost the code, but I experimented once with creating a derived button control that would emit a "doPostBack" script targetted to it's parent when placed in a templated control.  So instead of doing __doPostBack('grdData$ctl04$ctl00','') it would do __doPostBack('grdData','Delete$2').  That way the event could be raised as if it came from the gridview before instantiating the templated controls.  I wish I still had the code so I could show it to you, but essentially I just overrode the Render method.  There's probably a better way to do it, but I was just playing around with alternatives at the time.

     

     

    Check out this blog entry of mine: http://scottonwriting.net/sowblog/posts/1268.aspx. Here's the germane part:

    My student, Matt, wrote in to share the following workaround as well (I've not tested it; might not work in all browsers):

    My colleague Borys has proposed a fix.  I don't think I can explain it
    very well, but we think it has to do with a conflict between post data
    and viewstate.  I'll let the code speak for itself.

    Private Sub DataList1_ItemDataBound(ByVal sender As Object, ByVal e As
    System.Web.UI.WebControls.DataListItemEventArgs) Handles
    DataList1.ItemDataBound

                Dim b As Button = DirectCast(e.Item.Controls(1), Button)

                Dim evTarget As String = b.UniqueID.Replace(":", "$")

                Dim script As String = "__doPostBack('" + evTarget +
    "','');return(false)"

                b.Attributes.Add("onclick", script)

    End Sub

    Hope this helps someone, it was rather fun to examine and unearth the solution.

     Which looks like it's updating the __doPostBack call. No idea if this will be of help, but when I saw your post it reminded me of this old blog entry.

    HTH 

    Happy Programming!


    -- Scott Mitchell
    -- mitchell@4guysfromrolla.com
    -- http://scottonwriting.net/sowblog/
    -- http://www.4GuysFromRolla.com/ScottMitchell.shtml
  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    12-31-2006, 8:40 AM
    • Contributor
      2,035 point Contributor
    • valenumr
    • Member since 12-31-2006, 1:27 PM
    • Posts 372

    You could just use something a bit simpler, such as the following:

    <asp:ImageButton ID="bnDelete" runat="server" ImageUrl="~/Images/icon-delete.gif" CommandName="Delete" CausesValidation="false"

    CommandArgument='<%# DirectCast(DirectCast(Container,GridViewRow).NamingContainer, GridView).DataKeys(Container.DataItemIndex).Value %>' />

     

     

    This way you will always get the row's primary key whenever the button is clicked. The above is a little ugly, so if you like hand editing, you can use:

    <asp:ImageButton ID="bnDelete" runat="server" ImageUrl="~/Images/icon-delete.gif" CommandName="Delete" CausesValidation="false"

    CommandArgument='<%#  Databinder.Eval (Container.DataItem, "myKeyName" %>' />

  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    02-13-2007, 5:27 PM
    • Member
      35 point Member
    • KarenAnn
    • Member since 09-11-2006, 2:00 AM
    • Posts 34

    Can someone please confirm that this issue has now been resolved as per https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=243580?

    Or is the above work around the recommended approach?

    Thanks,

    Karen

  • Re: ObjectDataSource + GridView + Disabled ViewState = Major Concurrency Issues (possible solution)

    02-14-2007, 6:58 AM
    • Contributor
      2,750 point Contributor
    • RichardD
    • Member since 09-03-2002, 11:43 AM
    • Sussex, UK
    • Posts 361

    I haven't seen anything to indicate why this bug report has been closed, or whether this problem will be fixed in a future release.

    In my experience, Microsoft seem increasingly reluctant to fix any bugs in the framework for fear of breaking backwards compatibility. I've recently had several bug reports closed as "Won't Fix", quoting S. Somasegar's blog entry:

  • Another Concurrency Issue with Edits

    05-30-2008, 1:56 PM
    • Member
      4 point Member
    • nngco
    • Member since 05-30-2008, 5:33 PM
    • Posts 2

     I think I have found another little bug with Scott's implementation above.

    1.  User A and user B pull up the same page.

    2.  User A clicks to Edit a row.

    3.  User B clicks to Delete the row.

    4.  User A clicks to update the row. 

    5.  The consequence is that User A will actually update the row right beneath the row he/she was attempting to edit. 

    This is with the code Scott posted adjusted to also handle edits. 

    It seems that the rowcommand event fires after the DataKeys collection is internally repopluated on the update command.  Hence the row below the one intended for the update is updated. 

    Can someone verify or post an easy solution that might get around this while keeping viewstate off and using an objectdatssource?  The only work around I can think of is to store the datakey on an edit in another persisted state variable and read it back in on the datasource's onupdating command.  I guess this is better than storing the whole gridview in viewstate. 

    Any ideas would be much appreciated.
  • Re: Another Concurrency Issue with Edits

    08-22-2008, 9:14 AM
    • Member
      point Member
    • Caph
    • Member since 08-22-2008, 9:13 AM
    • Posts 1
    I've posted a complete solution on https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=243580
Page 1 of 1 (15 items)