Click here to Skip to main content
6,629,885 members and growing! (22,168 online)
Email Password   helpLost your password?
Web Development » ASP.NET Controls » Grid Controls     Intermediate License: The Code Project Open License (CPOL)

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

By Ferreri Gabriele (Megasoft78)

How to implement bulk edit with GridView without xxxDataSource (SqlDataSource, ObjectDataSource, etc.)
C# (C# 2.0), Javascript, CSS, HTML, XHTML, .NET (.NET 2.0), ASP, ASP.NET, ADO.NET, WebForms, Ajax, Dev
Posted:15 Jun 2008
Updated:22 Jun 2008
Views:39,365
Bookmarked:40 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
13 votes for this article.
Popularity: 4.07 Rating: 3.65 out of 5
1 vote, 7.7%
1
1 vote, 7.7%
2

3
4 votes, 30.8%
4
7 votes, 53.8%
5

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:

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:

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:

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:

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)

About the Author

Ferreri Gabriele (Megasoft78)


Member
I'm an Italian Software Developer from about 10 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.

Visit my small project:
http://www.codeplex.com/VisualDB
Occupation: Software Developer (Senior)
Company: sparesFinder
Location: Italy Italy

Other popular ASP.NET Controls articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 25 of 32 (Total in Forum: 32) (Refresh)FirstPrevNext
GeneralWhy not use Matt Dotson's DirtyRow method? Pinmemberrodneyjoyce3:21 6 Jun '09  
GeneralRe: Why not use Matt Dotson's DirtyRow method? PinmemberFerreri Gabriele (Megasoft78)1:18 7 Jun '09  
GeneralRe: Why not use Matt Dotson's DirtyRow method? Pinmemberrodneyjoyce21:48 7 Jun '09  
GeneralGet Value Using Dropdownlist Pinmemberpanux_s15:32 29 Mar '09  
GeneralRe: Get Value Using Dropdownlist Pinmemberpanux_s16:49 29 Mar '09  
Generaldidn't see pagination answered here Pinmemberatarime10:59 3 Mar '09  
GeneralRe: didn't see pagination answered here PinmemberFerreri Gabriele (Megasoft78)21:23 4 Mar '09  
GeneralNew line in empty datasource Pinmembereduseixas2:06 6 Jan '09  
AnswerRe: New line in empty datasource PinmemberFerreri Gabriele (Megasoft78)2:30 6 Jan '09  
GeneralRe: New line in empty datasource Pinmembereduseixas2:43 6 Jan '09  
GeneralRe: New line in empty datasource Pinmembereduseixas4:10 6 Jan '09  
GeneralRe: New line in empty datasource PinmemberTomIn018216:28 25 Jan '09  
QuestionUse dropdown list in a column Pinmembermariagp7:54 2 Dec '08  
AnswerRe: Use dropdown list in a column PinmemberFerreri Gabriele (Megasoft78)9:17 2 Dec '08  
QuestionRe: Use dropdown list in a column Pinmembermariagp10:45 2 Dec '08  
AnswerRe: Use dropdown list in a column PinmemberFerreri Gabriele (Megasoft78)11:24 2 Dec '08  
QuestionUsing Bulk Edit Gridview from VB Pinmemberxavier3339:33 29 Aug '08  
AnswerRe: Using Bulk Edit Gridview from VB PinmemberFerreri Gabriele (Megasoft78)4:22 30 Aug '08  
GeneralRe: Using Bulk Edit Gridview from VB Pinmemberxavier3333:27 31 Aug '08  
QuestionSlow in edit mode with large amount of Data > 100 rows Pinmember22255668:39 28 Aug '08  
AnswerRe: Slow in edit mode with large amount of Data > 100 rows PinmemberFerreri Gabriele (Megasoft78)9:11 28 Aug '08  
Generalpartial bulk edit? Pinmemberstockid15:14 27 Aug '08  
GeneralRe: partial bulk edit? PinmemberFerreri Gabriele (Megasoft78)21:15 27 Aug '08  
GeneralRe: partial bulk edit? Pinmemberstockid9:31 28 Aug '08  
GeneralRe: partial bulk edit? PinmemberFerreri Gabriele (Megasoft78)12:34 28 Aug '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 22 Jun 2008
Editor: Smitha Vijayan
Copyright 2008 by Ferreri Gabriele (Megasoft78)
Everything else Copyright © CodeProject, 1999-2009
Web09 | Advertise on the Code Project