Click here to Skip to main content
15,867,453 members
Articles / Web Development / XHTML
Article

Bulk Edit with GridView without xxxDataSource (SqlDataSource, ObjectDataSource, etc.)

Rate me:
Please Sign up or sign in to vote.
4.35/5 (16 votes)
22 Jun 2008CPOL4 min read 219.2K   7.3K   63   56
How to implement bulk edit with GridView without xxxDataSource (SqlDataSource, ObjectDataSource, etc.)

Screenshot2.jpg

Introduction

The ASP.NET GridView is a very amazing control, and it makes it very easy to implement editing on multiple rows data. Some days ago, I needed to implement bulk editing on GridView. I searched on the Internet and I found a very good article from Matt Dotson at MSDN.

Unfortunately, he uses SQLDataSource and I prefer to bind grids using the property DataSource and the method Databind. GridView is not really well designed to be used without a xxxDataSource (SQLDataSource, ObjectDataSource, etc.), especially for paging, sorting, and editing.

This tutorial is intended to explain how to implement an extended version of GridView that allows to edit/update multiple rows without any xxxDataSource.

Background

The main idea of bulk edit is quite similar to the control implemented by Matt Dotson, but I had to change most of the code because the GridView works differently using manual binding. While overriding some methods in the GridView, I found problems using manual binding because some objects are not properly initialized. I used Reflector and I discovered that most of the code within GridView strictly relies on the DataSourceID.

The control implementation

The class BulkEditGridView inherits from GridView, and it add/overrides the followings properties/methods:

  • BulkEdit: Boolean property used to maintain the state about bulk edit. This property stores the value into the ViewState.
  • CreateRow (override): used in conjunction with BulkEdit to make all rows editable.
  • BulkUpdate: used to call the GridView's RowUpdating event for all rows.
  • GetOldValue and GetNewValue: used to get the old and new values.

Overriding the method CreateRow and using the BulkEdit property, I can make all rows editable:

C#
protected override GridViewRow CreateRow(int rowIndex, int dataSourceIndex, 
          DataControlRowType rowType, DataControlRowState rowState)
{
    GridViewRow row;

    if (this.BulkEdit)
        row = base.CreateRow(rowIndex, dataSourceIndex, 
              rowType, rowState | DataControlRowState.Edit);
    else
        row = base.CreateRow(rowIndex, dataSourceIndex, rowType, rowState);

    return row;
}

The method BulkUpdate calls the update on all rows using the GridView method, UpdateRow:

C#
public void BulkUpdate()
{
    foreach (GridViewRow row in this.Rows)
    {
        this.UpdateRow(row.RowIndex, false);
    }
}

The methods GetOldValue and GetNewValue (use Generics) have two overloads, one each for the Template columns and DataBound columns. The old values come from the values available in the control from the cell. The new values comes from the Request collection, and are converted using the private method ConvertValue:

C#
private T ConvertValue<T>(string strValue)
{
    object value = default(T);

    if (strValue != null)
    {
        if (typeof(T) == typeof(string))
        {
            value = strValue;
        }
        else if (typeof(T) == typeof(int))
        {
            value = Convert.ToInt32(strValue);
        }
        else if (typeof(T) == typeof(double))
        {
            value = Convert.ToDouble(strValue);
        }
        else if (typeof(T) == typeof(bool))
        {
            if (strValue.ToLower() == "on" || strValue.ToLower() == 
                        "true" || strValue.ToLower() == "1")
                value = true;
            else
                value = false;
        }
        else if (typeof(T) == typeof(DateTime))
        {
            value = Convert.ToDateTime(strValue);
        }
    }

    return (T)value;
}

public T GetOldValue<T>(int rowIndex, int cellIndex)
{
    BoundField bf = this.Columns[cellIndex] as BoundField;
    
    T retVal = default(T);

    if (bf != null)
    {
        if (bf.ReadOnly)
        {
            DataControlFieldCell cell = 
              this.Rows[rowIndex].Cells[cellIndex] as DataControlFieldCell;
            retVal = ConvertValue<T>(cell.Text);
        }
        else
        {
            Control ctrl = this.Rows[rowIndex].Cells[cellIndex].Controls[0];

            if (ctrl.GetType() == typeof(TextBox))
            {
                retVal = this.ConvertValue<T>(((TextBox)ctrl).Text);
            }
            else if (ctrl.GetType() == typeof(CheckBox))
            {
                retVal = this.ConvertValue<T>(((CheckBox)ctrl).Checked.ToString());
            }
            else if (ctrl.GetType() == typeof(DropDownList))
            {
                retVal = this.ConvertValue<T>(((DropDownList)ctrl).SelectedValue);
            }
        }
    }
    else
    {
        throw new ArgumentException("The cell selected is not a DataBoundControl!");
    }

    return retVal;
}

public T GetOldValue<T>(int rowIndex, string controlName)
{
    Control ctrl = this.Rows[rowIndex].FindControl(controlName);
    
    T retVal = default(T);

    if (ctrl != null)
    {
        if (ctrl.GetType() == typeof(TextBox))
        {
            retVal = this.ConvertValue<T>(((TextBox)ctrl).Text);
        }
        else if (ctrl.GetType() == typeof(CheckBox))
        {
            retVal = this.ConvertValue<T>(((CheckBox)ctrl).Checked.ToString());
        }
        else if (ctrl.GetType() == typeof(DropDownList))
        {
            retVal = this.ConvertValue<T>(((DropDownList)ctrl).SelectedValue);
        }
    }
    else
    {
        throw new ArgumentException("The controlName not found!");
    }

    return retVal;
}

private T GetNewValue<T>(string uniqueID)
{
    string strValue = this.Page.Request[uniqueID];
    return ConvertValue<T>(strValue);
}

public T GetNewValue<T>(int rowIndex, int cellIndex)
{
    BoundField bf = this.Columns[cellIndex] as BoundField;

    if (bf != null)
    {
        if (bf.ReadOnly)
        {
            DataControlFieldCell cell = 
              this.Rows[rowIndex].Cells[cellIndex] as DataControlFieldCell;
            return ConvertValue<T>(cell.Text);
        }
        else
        {
            string uniqueID = this.Rows[rowIndex].Cells[cellIndex].Controls[0].UniqueID;
            return this.GetNewValue<T>(uniqueID);
        }
    }
    else
    {
        throw new ArgumentException("The cell selected is not a DataBoundControl!");
    }
}

public T GetNewValue<T>(int rowIndex, string controlName)
{
    string uniqueID = this.Rows[rowIndex].FindControl(controlName).UniqueID;
    return this.GetNewValue<T>(uniqueID);
}

Using the control

In the sample web project, I used a single table (tblProducts) in a SQLite database. I used SQLite because it is very powerful, small, fast, and because my free hosting doesn't have support for database. SQLite is a file based database with most of the features available in modern RDBMSs. It supports a large subset of SQL, Views, Triggers, etc. For additional details, refer to this link.

SQLite is an open-source project created in C language, but there exists a specific ADO.NET provider that makes it very easy to use it with .NET. You can download it from here.

The web project is composed of the following files:

  1. Default.aspx: The page containing the example to use BulkEditGridView.
  2. Progress.gif: Used within the UpdateProgress control (ASP.NET AJAX Framework).
  3. ProductEntity.cs: A simple class with properties containing the data for a single product (used to bind the BulkEditGridView manually).
  4. DataAccessLayer.cs: A Singleton class used to access the database. It retrieves the list of ProductEntity instances and saves the new data.

The project uses the ASP.NET AJAX framework (UpdatePanel) to make it all more nice and fast, but it's not required for the control. The Default.aspx page contains one instance of BulkEditGridView and three buttons (Edit, Update, and Cancel) used to change the state of the grid and to update the data. The grid implements paging using the built-in paging mechanism (I know it's not very smart, but it's very easy to implement! :) ). The most important part of the code is the following:

C#
private void RefreshButtons(bool editMode)
{
    if (editMode)
    {
        this.btnEdit.Visible = false;
        this.btnUpdate.Visible = true;
        this.btnCancel.Visible = true;
    }
    else
    {
        this.btnEdit.Visible = true;
        this.btnUpdate.Visible = false;
        this.btnCancel.Visible = false;
    }
}

protected void btnEdit_Click(object sender, EventArgs e)
{
    this.grdProducts.BulkEdit = true;
    RefreshGrid();
    RefreshButtons(true);
}

protected void btnUpdate_Click(object sender, EventArgs e)
{
    this.grdProducts.BulkEdit = true;
    RefreshGrid();

    this.grdProducts.BulkUpdate();

    this.grdProducts.BulkEdit = false;
    RefreshGrid();
    RefreshButtons(false);
}
    
protected void btnCancel_Click(object sender, EventArgs e)
{
    this.grdProducts.BulkEdit = false;
    RefreshGrid();
    RefreshButtons(false);
}

protected void grdProducts_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    int productID = this.grdProducts.GetNewValue<int>(e.RowIndex, 0);
    string productName = this.grdProducts.GetNewValue<string>(e.RowIndex, 
                                                       "txtProductName");
    double unitPrice = this.grdProducts.GetNewValue<double>(e.RowIndex, 2);
    bool discontinued = this.grdProducts.GetNewValue<bool>(e.RowIndex, 3);

    string oldProductName = this.grdProducts.GetOldValue<string>(e.RowIndex, 
                                                          "txtProductName");
    double oldUnitPrice = this.grdProducts.GetOldValue<double>(e.RowIndex, 2);
    bool oldDiscontinued = this.grdProducts.GetOldValue<bool>(e.RowIndex, 3);

    if (productName != oldProductName || unitPrice != 
             oldUnitPrice || discontinued != oldDiscontinued)
    {
        List<SQLiteParameter> parameters = new List<SQLiteParameter>();
        parameters.Add(new SQLiteParameter("@ProductID", productID));
        parameters.Add(new SQLiteParameter("@ProductName", productName));
        parameters.Add(new SQLiteParameter("@UnitPrice", unitPrice));
        parameters.Add(new SQLiteParameter("@Discontinued", discontinued));
        parameters.Add(new SQLiteParameter("@LastChange", DateTime.Now));

        DataAccessLayer.Instance.ExecuteQuery(
                        @"Update tblProducts set ProductName=@ProductName," + 
                        @" UnitPrice=@UnitPrice, Discontinued=@Discontinued, " + 
                        @"LastChange=@LastChange where ProductID=@ProductID", 
                        parameters.ToArray());
    }

In the Click event of the Edit button, I set the BulkEdit property of the grid to true, I bind the data, and I make the Edit button hidden and the Update and Cancel buttons visible. This action make all rows of grid in edit mode. The Click event on Update restores the edit mode for the grid (this is necessary to restore the control state), and the I call the BulkUpdate method of the grid. This method calls the RowUpdating event for each row in the grid. In RowUpdating, I use the methods GetOldValue and GetNewValue to compare the differences and save only the changed row. Both methods have two overloads to get the data from a simple DataBound column or from a Template column.

Points of Interest

Probably, this solution is not the best one because I have to manually compare the changes in the rows, but I haven't found a generic way to solve this problem. Any suggestions are welcome! :)

History

  • 15 June 2008 - First release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) sparesFinder
Italy Italy
I'm an Italian Software Developer from about 15 years.
I worked a long time in south Italy (where I was born) and after 2 years in Milan and an year in UK, I'm working remotely from Italy as Senior ASP.NET C# Developer using ASP.NET Ajax technology for a UK company.

Check out my personal blog:
http://techcookies.net/

and my first Android game (Fifteen Puzzle X):
https://play.google.com/store/apps/details?id=it.megasoft78.fifteenpuzzlex

Comments and Discussions

 
QuestionThe SQLLite is missing Pin
Member 1168498912-May-15 6:23
Member 1168498912-May-15 6:23 
AnswerRe: The SQLLite is missing Pin
Ferreri Gabriele (Megasoft78)13-May-15 22:49
Ferreri Gabriele (Megasoft78)13-May-15 22:49 
QuestionError while binding DropDownList.SelectedValue in BulkEditGridView. Pin
Dinesh Prajapati1-Feb-14 8:51
Dinesh Prajapati1-Feb-14 8:51 
AnswerRe: Error while binding DropDownList.SelectedValue in BulkEditGridView. Pin
Ferreri Gabriele (Megasoft78)2-Feb-14 22:30
Ferreri Gabriele (Megasoft78)2-Feb-14 22:30 
GeneralRe: Error while binding DropDownList.SelectedValue in BulkEditGridView. Pin
Dinesh Prajapati3-Feb-14 16:48
Dinesh Prajapati3-Feb-14 16:48 
GeneralNewValue OldValue Pin
dwilsonhcf26-May-10 6:56
dwilsonhcf26-May-10 6:56 
GeneralRe: NewValue OldValue Pin
Ferreri Gabriele (Megasoft78)26-May-10 7:42
Ferreri Gabriele (Megasoft78)26-May-10 7:42 
QuestionHow do I get the oldValue of a hidden keysys column ? Pin
dwilsonhcf25-May-10 9:30
dwilsonhcf25-May-10 9:30 
AnswerRe: How do I get the oldValue of a hidden keysys column ? Pin
Ferreri Gabriele (Megasoft78)25-May-10 10:12
Ferreri Gabriele (Megasoft78)25-May-10 10:12 
GeneralGridView ExtractRowValues() method [modified] Pin
jimbo31221-May-10 4:37
jimbo31221-May-10 4:37 
GeneralFirefox issue Pin
deejayns10-Feb-10 11:53
deejayns10-Feb-10 11:53 
GeneralRe: Firefox issue Pin
Ferreri Gabriele (Megasoft78)10-Feb-10 20:50
Ferreri Gabriele (Megasoft78)10-Feb-10 20:50 
GeneralRe: Firefox issue Pin
deejayns10-Feb-10 23:17
deejayns10-Feb-10 23:17 
GeneralRe: Firefox issue Pin
deejayns10-Feb-10 23:39
deejayns10-Feb-10 23:39 
GeneralRe: Firefox issue Pin
deejayns11-Feb-10 2:22
deejayns11-Feb-10 2:22 
GeneralRe: Firefox issue Pin
Ferreri Gabriele (Megasoft78)11-Feb-10 5:47
Ferreri Gabriele (Megasoft78)11-Feb-10 5:47 
QuestionOldValue and NewValue have the same new value!? Pin
deejayns26-Jan-10 2:06
deejayns26-Jan-10 2:06 
AnswerRe: OldValue and NewValue have the same new value!? Pin
deejayns26-Jan-10 3:23
deejayns26-Jan-10 3:23 
GeneralRe: OldValue and NewValue have the same new value!? Pin
Ferreri Gabriele (Megasoft78)26-Jan-10 8:26
Ferreri Gabriele (Megasoft78)26-Jan-10 8:26 
Generalgreat job Pin
deejayns16-Jan-10 13:42
deejayns16-Jan-10 13:42 
GeneralRe: great job Pin
Ferreri Gabriele (Megasoft78)16-Jan-10 22:30
Ferreri Gabriele (Megasoft78)16-Jan-10 22:30 
QuestionDateTime formatting issue in sample Pin
AmyMarisa6-Jan-10 23:30
AmyMarisa6-Jan-10 23:30 
AnswerRe: DateTime formatting issue in sample Pin
Ferreri Gabriele (Megasoft78)6-Jan-10 23:49
Ferreri Gabriele (Megasoft78)6-Jan-10 23:49 
GeneralRe: DateTime formatting issue in sample Pin
AmyMarisa7-Jan-10 23:15
AmyMarisa7-Jan-10 23:15 
QuestionWhy not use Matt Dotson's DirtyRow method? Pin
rodneyjoyce6-Jun-09 2:21
rodneyjoyce6-Jun-09 2:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.