0X00000303

Fix ERROR_CANTSCROLLBACKWARDS (0x00000303) – SQL scroll cursor fail

Windows Errors Intermediate 👁 3 views 📅 Jul 22, 2026

This error hits when your app tries to scroll backward through a dataset that only supports forward-only movement. The fix is switching to a scrollable cursor or buffering the results.

Quick answer: Change your cursor type from forward-only to scrollable (static, keyset, or dynamic) in your connection string or ADO command object. If you can't change the cursor, rewrite your code to process rows one by one or buffer them into an array.

Why this error happens

What's actually happening here is your application requested a backward scroll operation – like FetchPrior or MovePrevious – on a result set that was opened with a forward-only cursor. SQL Server and most relational databases default to forward-only cursors because they're fast and memory efficient. The server streams rows one at a time and discards them after sending. There's no way to go back unless you tell it to keep the data.

I see this most often in C# code using SqlDataReader (which is strictly forward-only) or in classic ASP/VB6 using ADO with the default cursor location set to server-side. Someone adds a "previous" button or tries to skip back a few rows, and boom – error 0x00000303 (ERROR_CANTSCROLLBACKWARDS from the ODBC provider) or SQLSTATE 34000.

Fix steps

  1. Identify where the backward scroll happens. Look for code using MovePrevious, GetPreviousRow, or FetchPrior. Search your codebase for "scroll", "back", "prior".
  2. Change cursor type in your connection string. If you're using ADO.NET, switch from SqlDataReader to SqlDataAdapter + DataSet. For ADO classic, set CursorType to adOpenStatic or adOpenKeyset.
    Example for ADO in VBScript:
    Set rs = Server.CreateObject("ADODB.Recordset")
    rs.CursorLocation = adUseClient   ' client-side gives better backward support
    rs.CursorType = adOpenStatic       ' or adOpenKeyset
    rs.Open "SELECT * FROM Orders", conn
    For C# using ADO.NET:
    using (SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Orders", conn))
    {
        DataSet ds = new DataSet();
        da.Fill(ds);   // now you can scroll forward and backward
        DataTable dt = ds.Tables[0];
    }
  3. If you can't change the cursor (maybe the provider doesn't support scrollable cursors), rewrite your logic to do a single forward pass and store rows in a local list or array. Then navigate that list instead of the original result set.
  4. Test with a small data set first. Scrollable cursors use more memory – they pull all rows to the client (if client-side) or hold them on the server (if server-side). Make sure your app can handle the size.

Alternative fixes if the main one fails

Sometimes switching cursor types isn't possible – maybe you're using a third-party ORM that controls the cursor. In that case:

  • Fetch all rows and close the reader. Load everything into a DataTable or List<T>, then close the original data reader. Work from the local copy.
  • Re-query with offset. Instead of scrolling backward, keep track of the current position and re-run the query with a ROW_NUMBER() or OFFSET/FETCH clause to get the previous page. This works with any forward-only cursor but adds database round-trips.
  • Check your provider's limitations. Some OLEDB providers for older databases (like Jet 4.0 or FoxPro) genuinely don't support scrollable cursors at all. In that case your only option is client-side buffering.

Prevention tip

If you're building a UI that needs backward navigation, don't use SqlDataReader – use a DataTable from the start. It's a common rookie mistake to think "I'll just use a reader and add buttons later." You won't. The reader is a firehose: once data passes through, it's gone. Decide before you write the first ExecuteReader whether you need bidirectional access. The reason this rule holds is that changing the cursor type after the fact often breaks your entire data access layer – you end up rewriting half your app.

Also, set CursorLocation = adUseClient in ADO or Mars = False in newer SQL Native Client connections. That forces the driver to buffer results on the client side, which gives you backward scrolling for free with a static snapshot. It uses more memory but it's the simplest fix for any legacy app.

Was this solution helpful?