0

I am using a GridView which could potentially display half a million records (depending on the options the user selects in filtering the data).

I want to prevent this, for optimization/performance purposes. If it would dramatically improve performance, I am willing to limit the result set to a few thousand or so records.

Would starting my query with a TOP (N) and ending with a OPTION(FAST N) be a good thing for this situation, or would it interfere with GridView's built-in optimization (I'm assuming, and I think I read it, that GridView only loads so many records at a time, as the user scrolls through).

So should I do something like this:

SELECT TOP (1000) [column list]
FROM [table]
WHERE [bla]
ORDER BY [Bla]
OPTION(FAST 100)

...or am I better off leaving it at

SELECT [column list]
FROM [table]
WHERE [bla]
ORDER BY [Bla]

...due to a possibility that my attempts at optimization will interfere with what the GridView is already handling on its own?

B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862
  • 1
    Without an `ORDER BY` the first query is going to give inconsistent results. – Thom A Oct 14 '20 at 15:18
  • Thanks; it does have an ORDER BY, actually. I'll update my question to reflect that. – B. Clay Shannon-B. Crow Raven Oct 14 '20 at 15:27
  • 1
    I understand that the dataset that is loaded in the GridView will be the same that will be rendered in the view, therefore if it loads hundreds of thousands of rows it will be something painful, for something like that I could use pagination in GridView, this question talks about its implementation from which row range to load: https://stackoverflow.com/questions/17979498/performance-issues-loading-large-data-set-into-c-sharp-gridview – Julián Oct 14 '20 at 15:42
  • 1
    Search for 'custom gridview paging'. – wazz Oct 14 '20 at 16:07
  • 1
    I would not use FAST N (query hints) unless necessary, checkout this post on sql authority: https://blog.sqlauthority.com/2020/02/11/sql-server-optionfast-n-hint-and-performance/ – Dipen Shah Oct 14 '20 at 20:35
  • 1
    Just to be clear it's the System.Web.UI.WebControls namespace GridView and you are you using C# and .NET Framework 4.8? You're using SQL Server 2019, or compatibility level 150? – SteveC Oct 16 '20 at 21:50
  • @SteveC: Yes; .NET Framework 4.7-something, I think. SQL Server Express. I don't know about compatibility level. – B. Clay Shannon-B. Crow Raven Oct 16 '20 at 22:53

2 Answers2

1

this way is good but use withe below way first add a object datasource to page and set connecting for this datasource and write below code and this code must write click button event

datasource1.SelectCommand = "SELECT TOP ("+txtuserfilter+") [column list] FROM [table] WHERE [bla] ORDER BY [Bla] OPTION(FAST 100)";
GridView1.DataSourceID = "datasource1";
Iman Javadi
  • 141
  • 5
1

The GridView loads all rows. As far as I know it does not optimize anything. And if you do not disable ViewState for the GridView (or the page it is on) all those rows are added to the ViewState, resulting in a HUGE page size of way too much MB's.

The paging in the GridView is nothing more than just showing the rows 100 - 200, while the rest still is loaded.

So I would do something like this (more info). Note that order by is required in this case.

SELECT [column list]
FROM [table]
WHERE [bla]
ORDER BY [Bla]
OFFSET [Offset] ROWS FETCH NEXT [Pagesize] ROWS ONLY

Now, how to implement this for a GridView. First, create a Stored Procedure. You could do it without one, but because you also would need the total number of rows you would need two requests to the database. In this example the data is required by a userID as the WHERE parameter.

CREATE PROCEDURE [dbo].[GridView_Paging]

    @offset int,
    @pagesize int,
    @userID int 

AS
BEGIN

    DECLARE @totalrows INT;

    -- you will need the total amount of rows for paging to work

    SELECT @totalrows = COUNT(itemID) FROM MyTable WHERE userid = @userID;

    --select the data and add the total rows also

    SELECT ID, name, @totalrows AS totalrows
    FROM MyTable
    WHERE userID = @userID 
    ORDER BY ID
    OFFSET @offset ROWS FETCH NEXT @pagesize ROWS ONLY;

END
GO

Then add a GridView to the aspx page and make sure ViewState is off (this saves network traffic). And add a Repeater that will function as the Pager.

<asp:GridView ID="GridView1" runat="server" EnableViewState="false"></asp:GridView>

<asp:Repeater ID="Pager_GridView" runat="server">
    <ItemTemplate>
        <asp:LinkButton ID="LinkButton1" runat="server" OnCommand="Pager_GridView_Command" 
            CommandArgument='<%# Container.ItemIndex %>'><%# Container.ItemIndex + 1 %>
        </asp:LinkButton>
    </ItemTemplate>
</asp:Repeater>

Then a function to call the stored procedure and add the data to the GridView and create the pager. I add the data to a DataTable first instead of directly to the GridView1.DataSource because I need the total number of rows.

public void LoadGridView(int page, int userID)
{
    int pagesize = 10;
    double totalrows = 0;

    int offset = page * pagesize;

    //create a connection
    using (var connection = new SqlConnection(Common.connectionString))
    using (var command = new SqlCommand("GridView_Paging @offset, @pagesize, @userID", connection))
    {
        //add the parameters
        command.Parameters.Add("@offset", SqlDbType.Int).Value = offset;
        command.Parameters.Add("@pagesize", SqlDbType.Int).Value = pagesize;
        command.Parameters.Add("@userID", SqlDbType.Int).Value = userID;

        //open the connection
        connection.Open();

        using (var reader = command.ExecuteReader())
        {
            var dt = new DataTable();
            dt.Load(reader);

            //add the data to the gridview
            GridView1.DataSource = dt;
            GridView1.DataBind();

            //get the total rows from the datatable
            if (dt.Rows.Count > 0)
                totalrows = (int)dt.Rows[0]["totalrows"];
        }
    }

    //calculate the number of pages
    var pages = Math.Ceiling(totalrows / pagesize);

    //create the pager by binding an array of the correct size to the repeater
    Pager_GridView.DataSource = new string[(int)pages];
    Pager_GridView.DataBind();
}

Then add the Repeater LinkButton command

protected void Pager_GridView_Command(object sender, CommandEventArgs e)
{
    LoadGridView(Convert.ToInt32(e.CommandArgument), 12345);
}

And finally Page_Load. Because I've disabled the ViewState, the data has to be bound on every page load and PostBack (which is also the same page load basically). If you would just put LoadGridView(0, 12345); then you would be double-hitting the database. Once in Page_Load and once you click a link in the pager. To prevent this, check if the __EVENTTARGET is from the Repeater

protected void Page_Load(object sender, EventArgs e)
{
    string eventTarget = Request.Form["__EVENTTARGET"];

    //check the eventtarget contains the repeater container to prevent double hitting
    if (string.IsNullOrEmpty(eventTarget) || !eventTarget.Contains(Pager_GridView.UniqueID))
    {
        LoadGridView(0, 12345);
    }
}
VDWWD
  • 35,079
  • 22
  • 62
  • 79