简体   繁体   中英

Asynchronously showing Stored Procedure PRINT statements within .NET Web Control

I've written a long stored procedure that I'm calling from an ASP.NET (3.5) page.

The stored procedure looks something like this:

CREATE PROCEDURE csi_CourseEval_Export
   @year
   @term
   @use_case
AS
BEGIN
   SET NOCOUNT ON
   PRINT 'Building candidate course list'
   .... -- do stuff
   PRINT ''
   RAISERROR('1', '0', 1) WITH NOWAIT -- flush buffer and indicate step 1 reached
   PRINT ''
   .... -- do more stuff
   PRINT ''
   RAISERROR('2', '0', 1) WITH NOWAIT -- flush buffer and indicate step 2 reached
   PRINT ''
   .... -- do more stuff
   PRINT ''
   RAISERROR('3', '0', 1) WITH NOWAIT -- flush buffer and indicate step 3 reached
   PRINT ''
   .... -- do more stuff
   .... -- do LOTS more stuff, including steps 4 through 8
   PRINT ''
   RAISERROR('9', '0', 1) WITH NOWAIT -- flush buffer and indicate step 9 reached
   PRINT ''       

   RAISERROR('DONE', '0', 1) WITH NOWAIT -- flush buffer and indicate completion

END

And my C# codebehind has the following properties and methods that are relevant:

    private SqlConnection db;
    private string exportState = "";
    private static string jdbConnectionString = "Data Source=...;Initial Catalog=...;Persist Security Info=True;Asynchronous Processing=true;User ID=...;Password=...";

    private void ShowData(GridView display, string table)
    {
        display.DataSource = new SqlDataSource(CoursEval.jdbConnectionString, "SELECT * FROM ##" + table);
        display.DataBind();
    }

    private void SetControls(bool enable)
    {
        RunExport.Enabled = enable;

        if (enable)
            RunExport.Text = "Run";
        else
            RunExport.Text = "Running";

        UseCase.Enabled = enable;
        Year.Enabled = enable;
        Term.Enabled = enable;
    }

    private void LogSQL(string message)
    {
        if (message.Length == 1 || message.Equals("DONE"))
        {
            exportState = message;
        }
        SQL_Log.Text += "<br />" + message.Replace(" ", "&nbsp;").Replace("\n", "<br />");
    }

    protected void RunExport_Click(object sender, EventArgs e)
    {
        db = new SqlConnection(jdbConnectionString);

        db.FireInfoMessageEventOnUserErrors = true;            
        db.InfoMessage += delegate(object ds, SqlInfoMessageEventArgs de)
        {
            LogSQL(de.Message);              
        };
        db.Open();

        SqlCommand cmd = new SqlCommand("csi_CourseEval_Export", db);
        cmd.Parameters.Add("@year", SqlDbType.VarChar).Value = Year.SelectedValue;
        cmd.Parameters.Add("@term", SqlDbType.VarChar).Value = Term.SelectedValue;
        cmd.Parameters.Add("@use_case", SqlDbType.VarChar).Value = UseCase.SelectedValue;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.CommandTimeout = 0;

        SetControls(false);
        cmd.ExecuteNonQuery();

        ShowData(ShowCourses, "Courses");
        ShowData(ShowStudents, "Students");
        ShowData(ShowFaculty, "Faculty");

        ResultsPanel.Visible = true;

        db.Close();
        SetControls(true);
    }

The following System.Web.UI.WebControls.* form objects are relevant:

SQL_Log                                - Label
RunExport                              - Button
UseCase, Year and Term                 - DropDownList 
ShowCourses, ShowFaculty, ShowStudent  - GridView
ResultsPanel                           - Panel

The symptom:
no matter what I do, the SQL_Log label only updates once, at the very end of the stored procedure, while everything else is showing up.

The desired behavior:
SQL_Log label should update asynchronously, letting the user see how far along the stored procedure has come.

What am I doing wrong?

EDIT: This is a partial answer:

Updating to use asynchronous SqlCommand (BeginExecuteNonQuery onstead of ExecuteNonQuery) gets me to the first RAISERROR, but then I don't receive any further InfoMessage events after EndExecuteNonQuery is called, even though the Stored Procedure is still running at that point.

RunExport_Click now looks like:

    protected void RunExport_Click(object sender, EventArgs e)
    {
        db = new SqlConnection(CoursEval.jdbConnectionString);

        db.FireInfoMessageEventOnUserErrors = true;            
        db.InfoMessage += delegate(object ds, SqlInfoMessageEventArgs de)
        {
            LogSQL(de.Message);              
        };
        db.Open();

        SqlCommand cmd = new SqlCommand("csi_CourseEval_Export", db);
        cmd.Parameters.Add("@year", SqlDbType.VarChar).Value = Year.SelectedValue;
        cmd.Parameters.Add("@term", SqlDbType.VarChar).Value = Term.SelectedValue;
        cmd.Parameters.Add("@use_case", SqlDbType.VarChar).Value = UseCase.SelectedValue;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.StatementCompleted += GetExportResults;
        cmd.CommandTimeout = 0;

        SetControls(false);

        cmd.BeginExecuteNonQuery(new AsyncCallback(FinishExport), cmd);

    }

And I now have three additional functions, ShowResults, GetExportResults and FinishExport:

    private void ShowResults()
    {
        ShowData(ShowCourses, "Courses");
        ShowData(ShowFaculty, "Faculty");
        ShowData(ShowFacultyEnrollment, "FacultyEnrollment");
        ShowData(ShowStudents, "Students");
        ShowData(ShowStudentEnrollment, "StudentEnrollment");

        ResultsPanel.Visible = true;

    }


    private void FinishExport(IAsyncResult result)
    {
        if (result.IsCompleted)
        {
            SqlCommand cmd = (SqlCommand)result.AsyncState;

            cmd.EndExecuteNonQuery(result);
        }
        else
        {
            RunExport.Text = exportState;
        }
    }

    private void GetExportResults(object sender, StatementCompletedEventArgs e)
    {
        ShowResults();

        db.Close();
        SetControls(true);
    }

Additionally, LogSQL now looks like this:

    private void LogSQL(string message)
    {
        if (message.Length == 1 || message.Equals("DONE"))
        {
            exportState = message;
        }

        SQL_Log.Text += "<br />" + message.Replace(" ", "&nbsp;").Replace("\n", "<br />");
        ProgressPanel.Update();
    }

where ProgressPanel is the AJAX-driven UpdatePanel that everything is loaded within.

The script now reaches the stage 2 RAISERROR, at which point FinishExport fires and I receive no further InfoMessage events. GetExportResults never fires, so the whole page basically hangs at that point. What am I missing?

There is no way to update a label asynchronously - web applications don't work that way. When your page executes, it sends HTML to the browser, and then is destroyed. There is no simple process by which you can continue to send updated HTML to the browser.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM