.NET Discussion

.NET Issues, Problems, Code Samples, and Fixes

ASP.NET: How To Use BulkEditGridview To Save Hours In Database Editing


The GridView is a fantastic, versatile, and powerful tool. Unfortunately, it is also unrealistic in several aspects. Say you have a page in your website’s administrative back-end that is to update all of your prices for your products. With a typical GridView, if you wanted to update, say, 10 of these prices, you will require at minimum 20 postbacks, which depending on the speed of your server can be a lengthy process. Now imagine that you have over 1000 lines to edit. 2000 postbacks, minimum… sound like a daunting task that may take you hours in just waiting for your server to respond?

What if I told you that you could edit an unlimited number of records in a GridView with one click, one postback, and one call to your database, and that only the records you modified would be updated? What if I told you it was way, way easier than you think? You may think I am crazy at first if you have any experience with GridViews, but let me introduce you to the panacea of GridView woes, a little tool created by Matt Dotson called BulkEditGridView (more recently moved to this location on CodePlex). I have implemented this tool into nearly every editing application I have that requires a GridView and it has shaved not minutes or hours, but days of off time waiting for the server to post back.

I have been using this tool for quite some time now, but have waited until I had a full understanding of its power and nuances before I posted about it.  The links I provided you will give you more background on the why and how its production came about (and where to download it), but the documentation on its use is a bit on the scattered side. That’s where I come in!

To give you a bit of perspective, the BulkEditGridView is little more than a custom user control that inherits the GridView. However, the functionality it provides is considered more “real world applicable”, because on binding, it places every row in the GridView in Edit mode automatically. You may then either tab through columns or click through records you wish to edit, and then once you’re done with ALL your editing, you click your designated save button and everything is updated! It also knows which rows were edited and stores them in a Generics List Of(GridViewRow) called DirtyRows, which can be accessed programmatically.

Enough already, you say. How do I use it? Here’s a step-by-step of how I have implemented it. Please note that this is how I have implemented it. There may be better ways, but this is how it has worked for me. In this documentation, I am going to assume you have downloaded and installed the .dll for this control.

Step 1: Add BulkEditGridView to your .aspx page

Remember, it is essentially a GridView, so add it like you would to any other page. You may specify bound columns, template columns, or anything of the like the same way you would a regular GridView. The only extra property you must specify is the SaveButtonId property. This is just a button you have placed on your page that when pressed will make the BulkEditGridView work its magic.

Step 2: Add Save Button to page

As mentioned in the previous step, the BulkEditGridView requires that you specify which button is to be pressed for it to run. Any codebehind for this button is not required, but I typically add some catch-all code for when no edits have been made:

Protected Sub btnSaveChanges_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
	If BulkEditGridView1.DirtyRows.Count = 0 Then
		litMsg.Text = "No bulk updates made."
	End If
End Sub

Step 3: Define your RowUpdating Event Handler

This is where all the magic happens. When your Save Button is pressed, if you have any DirtyRows the RowUpdating event will fire for each row in that collection. You should create your RowUpdating event the same way you would for a regular GridView, but include the following modifications:

  1. Declare a Static counter variable (Static count As Integer), and immediately increment it at the beginning of your event handler. This will come in handy later.

  2. Declare a Static sql string variable (Static sql As String) or System.Text.StringBuilder if you are looking to optimize. This is where you will store all of your UPDATE strings for your database so that once you have iterated through all your rows, you may send them to your database to be run all at once. In other words, rather than update your database every time the RowUpdating event is called, return the sql string from your update function that you would typically run and add it on to a growing string of calls to make.

  3. At the end of the RowUpdating event, include some conditional code like:

    If count = BulkEditGridView1.DirtyRows.Count Then
    	Dim rowIndexes As New StringBuilder
    	For Each row As GridViewRow In BulkEditGridView1.DirtyRows	'find out which rows were updated
    		rowIndexes.Append(row.RowIndex + 1 & ", " ) 'real count, not 0 based
    	Next
    	rowIndexes.Remove(rowIndexes.Length - 2, 2)	'get rid of final comma
    	msg = "Rows " & rowIndexes.ToString & " have been updated."
    	AddOrUpdateData(sql.ToString) 'your function to call your database
    	BindGridView() 'your function to bind your GridView
    	litMsg.Text = msg
    End If
    

    The preceding code does four things: it checks to see if it has completed running the RowUpdating for each modified row (remember that static count variable?), it iterates back through the DirtyRows so that you may relay back to the user which rows were updated, it updates your database using one call (the AddOrUpdateData function, which is however you call your database to make updates), and it re-binds your GridView. NOTE: when building your SQL string, remember to end your separate UPDATE commands with a semicolon!

Some notes to consider:

  • If you are using template fields, do NOT include controls you wish to modify (textboxes, etc) inside of a Panel control. For some reason it will not detect them. What I did to get around this (if I wanted to display a panel on RowDataBound based on a variable, for instance) is I created two Literals: one where I would want to place the <div style="display:none;"> and one for the closing div tag.

  • At least for me, Intellisense doesn’t work for the BulkEditGridView in the .aspx sourcecode edit mode, so either know your stuff or drop a regular GridView into your page first, build it the way you want, and then copy and paste the guts into a BulkEditGridView once you’re done.

  • Once you update all your items and rebind your BulkEditGridView, it will show up again in edit mode. To toggle this “Bulk Edit” mode, I have sometimes created one BulkEditGridView and one normal GridView on the same page, and then a button to bind and show one and hide the other. For me it has created a seamless user experience and only costs you one postback.

And that’s it! I know this is a long post, but when you get used to using this amazing control, you will see that it boils down to only a few different things you need to change to get it working for you.

kick it on DotNetKicks.com

Advertisements

April 6, 2008 - Posted by | ASP.NET, Generics, GridView, MySQL, Tips & Tricks |

20 Comments »

  1. I am using BulkEditGridView in a multi-page data-entry site. I set SaveButtonID=”Button1″ where Button1 is a WebControls.Button labeled “SAVE”. It works as it is supposed to. After saving the data, the user must click a WebControls.HyperLink control with NavigateUrl=”~/MyNextPage.aspx” to get to the next page to enter more data. I tried to combine the process of saving data and moving to the next page by placing VB statement Response.Redirect(“~/MyNextPage.aspx”) in the click event of Button1. This sends the user to the next page — but does not save the data.

    If it is possible to combine the two tasks into a “Continue” operation triggered by a single click, I would prefer that it be done in the HyperLink, although using the Button would still be an improvement. Any ideas? Thanks!

    (Clearly this type of operation is possible, since I have seen it used on a number of sites — but perhaps they are using something other than ASP.NET…)

    Comment by Keith Brown | June 12, 2008 | Reply

  2. Ha! I see my previous post displays with unintended winking smiley-faces in places where I used a right-paren. Guess I will have to avoid right-parens in the future — unless I am, in fact, winking… Thanks!

    Comment by Keith Brown | June 12, 2008 | Reply

  3. Keith –

    If you read my Step 3 in this post, you will see how I’ve included conditional code in the RowUpdating event so that whatever I need to happen happens when all the rows are done updating.

    Basically, just put your redirect statement at the end of everything, and you should be ok.

    Thanks for the comment(s) 😉

    Comment by Some.Net(Guy) | June 12, 2008 | Reply

  4. Great! Getting close! I put the following in the RowUpdating event:

    Static count as integer
    count = count + 1
    MsgBox(count & ” of ” & GridView1.DirtyRows.Count)
    If count = GridView1.DirtyRows.Count Then Response.Redirect(“~/MyNextPage.aspx”)

    It goes through each dirty row and then moves me to my next page, but does not update the final row. That is, if I modify rows 1, 2 and 3, only rows 1 and 2 are updated in the database. Something simple, I’m guessing?

    Comment by Keith Brown | June 12, 2008 | Reply

  5. Assuming we can get this working with the Button, do you see any possibility that it could be made to work with a single click on a HyperLink? I’m thinking that, since the BulkEditGridView SaveButtonID is expecting a Button, there will always need to be a Button control present, but maybe it can be made invisible and be triggered programmatically from the HyperLink? Maybe? Thanks again for your help.

    Comment by Keith Brown | June 12, 2008 | Reply

  6. keith – use a link button!

    Comment by Some.Net(Guy) | June 12, 2008 | Reply

  7. as for your rows not all updating – increment your count variable at the end, not the beginning.

    Comment by Some.Net(Guy) | June 12, 2008 | Reply

  8. If I move the increment statement below the redirect, the value of count at the IF test goes from 0 to 1 to 2, instead of 1 to 2 to 3. With 3 dirty rows, and a maximum count value of 2, the rows all update but the redirect never fires.

    Comment by Keith Brown | June 12, 2008 | Reply

  9. keith – start your static var at 1

    static count as integer = 1

    Comment by Some.Net(Guy) | June 12, 2008 | Reply

  10. Finally got this working by using a different event, RowUpdatED rather than RowUpdatING. I am not doing any of my own SQL to update my database, just depending on the GridView. So all I needed was a way to check that all the row updates had been completed and then redirect to the next page. Looks like the update to the final row was not completing since I was forcing the redirect during the update, not after. Anyway, you were very helpful in pointing me in the right direction. Thanks very much for your time!

    Comment by Keith Brown | June 13, 2008 | Reply

  11. Is it possible to set a fixed width for the input fields? Any other modifications / validations on columns would be nice also.
    I used the VB version of this control (with a little alteration (making dirtyrows list public as in more recent C# version). Does anyone have a recent copy of a VB version of the control?

    Here is how I inserted bulkeditgridview:

    Comment by ric | July 9, 2008 | Reply

  12. cont’d:

    Comment by ric | July 9, 2008 | Reply

  13. Ric –

    The way I set the width for the input fields is by using CSS. If you give your BEGV a class, say class=”begv”, then you can set in your CSS .begv input {width: 100px;}. Note this will also affect your buttons, so be careful with your widths. As for VB version, I don’t code in C#, so this is the VB version! If you’re looking for a newer one, check the links I posted… You may be able to find your way there, although I don’t know if there is a newer one.

    Thanks for the comment 🙂

    Comment by Some.Net(Guy) | July 9, 2008 | Reply

  14. I just started implementing the BulkEditView and have run into two issues, hoping someone else has eitehr run into it or can help.

    1st Issue) Based on some rules, I am disabling cells in rows in the RowDataBound1, this works perfect, however, when i access and check the enabled property in a Custom Validator, all disabled cells are enabled = true, I know I am on the correct rows because all the data in cells and rows are correct. Since i’m on a time crunch, i’ve added another hidden column and am updating it in the click-changed event (one column is a checked field), this works great, but i hate not knowing why i’m getting incorrect values for the enabled (see above) in the 1st place.

    2nd Issue) Now that i have the Custom validator working correct (with the hidded field), when i enter bad data that the validator should catch, it does, but does not display the error in the ValidationSummary and stop, it continues on to the Row Updating event…

    Appreciate any responses.

    Comment by Preston | September 15, 2008 | Reply

  15. Preston –

    Are you sure you’re checking the “Enabled” property AFTER you’re disabling it? It depends on what point in the page lifecycle you’re performing these operations.

    As for #2, part of the BulkEditGridview is that it continuously goes to the next RowUpdating event, which is why it’s so useful. My suggestion is to create a static variable (Static isValid As Boolean = True) and as soon as something is invalidated, set it to false. Make your RowUpdating run a check to make sure this variable is true, and if not, just skip.

    Let me know how that works out for you!

    Comment by Some.Net(Guy) | September 15, 2008 | Reply

  16. Hi !! Thanks for replying…

    I am changing the enable property in the RowDataBound event (see code below).

    Yes, Thanks, I can add a Var as you suggested but i’m not sure it solves my prob, I would like for no update to happen if any of the Custom Validators fail and not even go to the RowUpdating, also when my data fails the Custom Validation, it does not show the error message in the validation Summary.
    ********************************************************** protected void grdProfileData_RowDataBound1(object sender, GridViewRowEventArgs e)
    {
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
    Profile drv = (Profile)e.Row.DataItem;
    if (drv.eff_date == “”)
    {
    e.Row.Cells[0].Enabled = true;
    e.Row.Cells[3].Enabled = true;
    }
    else
    {
    e.Row.Cells[0].Enabled = false;
    e.Row.Cells[3].Enabled = false;
    }
    if (drv.exp_date == “”)
    {
    e.Row.Cells[4].Enabled = true;
    }
    else
    {
    e.Row.Cells[4].Enabled = false;
    }
    }
    }
    **********************************************************
    I am checking them in the CustomValidator (see code below).
    )
    **********************************************************
    protected void CustomValidator1_ServerValidate(object source, ServerValidateEventArgs args)
    // check if previously not checked row, has data (eff_date/exp_date) but not checked
    // add this because of issue with checking enabled/disabled status
    // cdummy_sele.Text, comes in selected = 1
    // cdummy_sele.Text, comes in NOT selected = 0
    // cdummy_sele.Text, user selects = 2
    {
    CustomValidator cCustomV = (CustomValidator)source;
    GridViewRow cRow = (GridViewRow)cCustomV.Parent.Parent;

    CheckBox cCheckBox = (CheckBox)cRow.FindControl(“Editselected”);
    TextBox cEff_Date = (TextBox)cRow.FindControl(“Editeff_date”);
    TextBox cExp_Date = (TextBox)cRow.FindControl(“Editexp_date”);
    TextBox cdummy_sele = (TextBox)cRow.FindControl(“Editdummy_sele”);

    // if (cCheckBox.enabled == true)
    if (cdummy_sele.Text != “1”)
    {
    if ((cEff_Date.Text != “” || cExp_Date.Text != “”) && (cdummy_sele.Text == “0”))
    {
    args.IsValid = false;
    }
    else
    {
    args.IsValid = true;
    }
    }
    else
    {
    args.IsValid = true;
    }
    }
    **********************************************************

    Comment by Preston | September 16, 2008 | Reply

    • Hi Preston,
      I’m having the same problem as you did with the server validate. How did you manage to solve this ?

      Comment by Eileen Eby | September 16, 2012 | Reply

  17. Preston –

    I don’t think you can stop the RowUpdating event from iterating once it’s started… that may be part of the BEGV’s programming. I still think your best solution is to use a static variable and check against it, and if it’s false then just skip any sort of programming.

    Because the BEGV is kind of a unique control, I wouldn’t know where to start diagnosing your custom validator issues. It’s possible that your args keep getting reset from true to false to true again as they iterate through your rows rather than holding one value, but that’s all I can think of.

    Good luck, and let me know what the solution is when you find it!

    Comment by Some.Net(Guy) | September 16, 2008 | Reply

  18. I have been trying to use bulkeditgridview with a dropdownfield. I use the modified dropdownfield.cs since that resolved my problem of not getting a dropdown in the insert row; however, now upon inserting, it inserts only the value in the dropdown and values for all other fields comes out blank. Have been struggling with this for days now but haven’t been able to find a solution.
    Appreciate any responses.

    Comment by aarvi | May 5, 2011 | Reply

  19. I am not sure i understand your article completely but where is the button which edits all the rows within the grid? How can you open all the grid rows for editing???

    Comment by .net _coder | March 6, 2012 | Reply


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: