Question

I'm using an ObjectDataSource with a SPGridView (a SharePoint control based off a GridView), and looking at the logs, the SelectMethod is being called nine times - which is 8 times more than it really needs to.

I was wondering if anyone knew exactly under what conditions an ObjectDataSource object will call the Select Method, or a big list of things to check / any suggestions on optimizing this? It's DataBound so does that mean it's all down to the SPGridView?

I know that the selectcountmethod invokes it once (as per this SO answer), but I dont really quite get why it needs to call the select method so many times. Am I binding at the wrong step? Initializing at the wrong step? I also have paging, sorting and filtering enabled...Any tips would be great :)

EDIT: I added some logging code to the events, and it looks like the DataBinding event on the GridView is being hit multiple times, so does that mean internally the SPGridView is calling DataBind() multiple times? The only DataBind() I call is on the Render event in the user control, which is only raised once.

Logging trace:

ODS_Selecting
ODS_ObjCreating
ODS_Selecting
Gridview_DataBinding
ODS_Selecting
ODS_ObjCreating
ODS_Selecting
Gridview_DataBinding
GridView_RowDataBound
...
GridView_RowDataBound
ODS_Selecting
ODS_ObjCreating
ODS_Selecting
Gridview_DataBinding
GridView_RowDataBound
...
GridView_RowDataBound
ODS_Selecting
ODS_ObjCreating
ODS_Selecting
Gridview_DataBinding
GridView_RowDataBound
...
GridView_RowDataBound
ODS_Selecting
ODS_ObjCreating
ODS_Selecting
Gridview_DataBinding
GridView_RowDataBound
...
GridView_RowDataBound
ODS_Selecting
ODS_ObjCreating
ODS_Selecting
Gridview_DataBinding
GridView_RowDataBound
...
GridView_RowDataBound
ODS_Selecting
ODS_ObjCreating
ODS_Selecting
Gridview_DataBinding
GridView_RowDataBound
...
GridView_RowDataBound
ODS_Selecting
ODS_ObjCreating
ODS_Selecting
Gridview_DataBinding
GridView_RowDataBound
...
GridView_RowDataBound
ODS_Selecting
ODS_ObjCreating
ODS_Selecting
Gridview_DataBinding
GridView_RowDataBound
....
GridView_RowDataBound
ODS_Selecting
ODS_ObjCreating
ODS_Selecting
Gridview_DataBinding
GridView_RowDataBound
....
GridView_RowDataBound

And also, here's a big chunk of code!

.ascx:

<SharePoint:SPGridView ID="appGridView" DataSourceID="appODS" AutoGenerateColumns="false" 
AllowPaging="true" PageSize="10" AllowSorting="true"
AllowFiltering="true" FilterDataFields=",,,,TimeElapsed,,," FilteredDataSourcePropertyName="FilterExpression" FilteredDataSourcePropertyFormat="{1} = '{0}'"
runat="server" />
<wc:ObjectDataSource ID="appODS" runat="server" />
<SharePoint:SPGridViewPager ID="appGridViewPager" runat="server" GridViewId="appGridView" />

.ascx.cs:

public partial class MyUserControl : UserControl {

    //http://www.reversealchemy.net/blog/2009/05/01/building-a-spgridview-control-part-1-introducing-the-spgridview/
    //http://www.reversealchemy.net/2009/05/24/building-a-spgridview-control-part-2-filtering/

    protected override void OnInit(EventArgs e)
    {
        appODS.EnablePaging = true;
        appODS.SelectMethod = "GetMyDataTable";
        appODS.SelectCountMethod = "GetMyDataTableCount";
        appODS.StartRowIndexParameterName = "rowStart";
        appODS.MaximumRowsParameterName = "rowCount";

        appODS.SelectParameters.Add(new Parameter { Name = "myParam", DefaultValue = "test" });  
        appODS.TypeName = this.GetType().AssemblyQualifiedName;
        appODS.ObjectCreating += new ObjectDataSourceObjectEventHandler(appODS_ObjectCreating);

        appGridView.RowDataBound += new GridViewRowEventHandler(appGridView_RowDataBound);
        appODS.Filtering += new ObjectDataSourceFilteringEventHandler(appODS_Filtering);
        appGridView.Sorting += new GridViewSortEventHandler(appGridView_Sorting);
    }


    //https://stackoverflow.com/questions/1351578/spgridview-data-and-correct-method-of-ensuring-data-is-safe
    protected override void OnPreRender(EventArgs e)
    {
        if (!string.IsNullOrEmpty(appODS.FilterExpression))
        {
            appODS.FilterExpression = string.Format(
                appGridView.FilteredDataSourcePropertyFormat,
                appGridView.FilterFieldValue.Replace("'", "''"),
                appGridView.FilterFieldName
                );
        }

        base.OnPreRender(e);
    }


    private void appODS_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
    {
        e.ObjectInstance = this;
    }

    protected sealed override void Render(HtmlTextWriter writer)
    {
        GenerateColumns();
        appGridView.DataBind();
        base.Render(writer);
    }

    public DataTable GetMyDataTable(string myParam, int rowStart, int rowCount)
    {
        DataTable dt = new DataTable();

        dt.Columns.Add("ID", typeof(Int32));
        // more columns here 
        dt.Columns.Add("TestColumn", typeof(decimal));


        List<Stuff> things = svc.GetStuff();


        foreach (Stuff thing in things)
        {
            dt.Rows.Add(
                thing.Blah, thing.Blah2);
        }

        return dt;
    }

    public int GetMyDataTableCount(string myParam)
    {
        return 10003; //faked out for now           
    }

    private void GenerateColumns()
    {
        BoundField column = new BoundField();
        column.DataField = "ID";
        column.SortExpression = "ID";
        column.HeaderText = "ID";            
        appGridView.Columns.Add(column);

        //more

        column = new BoundField();
        column.DataField = "TestColumn";
        column.SortExpression = "TestColumn";
        column.HeaderText = "TestColumn";
        appGridView.Columns.Add(column);

    }


    ////filtering
    protected sealed override void LoadViewState(object savedState)
    {
        base.LoadViewState(savedState);

        if (Context.Request.Form["__EVENTARGUMENT"] != null &&
             Context.Request.Form["__EVENTARGUMENT"].EndsWith("__ClearFilter__"))
        {
            // Clear FilterExpression
            ViewState.Remove("FilterExpression");
        }
    }

    private void appODS_Filtering(object sender, ObjectDataSourceFilteringEventArgs e)
    {
        ViewState["FilterExpression"] = ((ObjectDataSourceView)sender).FilterExpression;
    }

    private void appGridView_Sorting(object sender, GridViewSortEventArgs e)
    {
        if (ViewState["FilterExpression"] != null)
        {
            appODS.FilterExpression = (string)ViewState["FilterExpression"];
        }
    }

    private void appGridView_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (sender == null || e.Row.RowType != DataControlRowType.Header)
        {
            return;
        }

        SPGridView grid = sender as SPGridView;

        if (String.IsNullOrEmpty(grid.FilterFieldName))
        {
            return;
        }

        // Show icon on filtered column
        for (int i = 0; i < grid.Columns.Count; i++)
        {
            DataControlField field = grid.Columns[i];

            if (field.SortExpression == grid.FilterFieldName)
            {
                Image filterIcon = new Image();
                filterIcon.ImageUrl = "/_layouts/images/filter.gif";
                filterIcon.Style[HtmlTextWriterStyle.MarginLeft] = "2px";

                // If we simply add the image to the header cell it will
                // be placed in front of the title, which is not how it
                // looks in standard SharePoint. We fix this by the code
                // below.
                Literal headerText = new Literal();
                headerText.Text = field.HeaderText;

                PlaceHolder panel = new PlaceHolder();
                panel.Controls.Add(headerText);
                panel.Controls.Add(filterIcon);

                e.Row.Cells[i].Controls[0].Controls.Add(panel);

                break;
            }
        }
    }
}
Was it helpful?

Solution

I eventually worked it out by whipping out reflector and doing a bit more digging - it turns out that I didn't even need the .DataBind() call in the Render method, since SPGridView inherits from BaseDataBoundControl, which ensures DataBind is called where necessary - from what I understand on MSDN, it gets called OnPreRender.

It turns out that by generating the columns in the Render event (the appGridView.Columns.Add(column);), each time I modified the appGridView the DataBind() method ended up being called again - so the Render event is far too late to be setting up the gridview Columns, since by that stage it expected everything done already and every time I added a new one it had to "rebind" to get the data.

This is a massive performance issue which you should be aware of if you're following the otherwise decent example here on how to set up a SPGridView with ObjectDataSource.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top