This question already has answers here:
Capture Stored Procedure print output in .NET
(3 answers)
Closed 6 years ago.
When executing scripts in SQL Server Management Studio, messages are often generated that display in the message window. For example when running a backup of a database:
10 percent processed.
20 percent processed.
Etc...
Processed 1722608 pages for database 'Sample', file 'Sampe' on file 1.
100 percent processed.
Processed 1 pages for database 'Sample', file 'Sample_Log' on file 1.
BACKUP DATABASE successfully processed 1722609 pages in 202.985
seconds (66.299 MB/sec).
I would like to be able to display these message in a C# application that is running SQL scripts against a database. However, I cannot figure out how to get a handle on the message output from SQL as it is generated. Does anybody know how to do this? It doesn't matter to me which connection framework I have to use. I'm relatively comfortable with LINQ, NHibernate, Entity Framework, ADO.Net, Enterprise Library, and am happy to learn new ones.
Here is the example code I tried and it works for me.
http://www.dotnetcurry.com/ShowArticle.aspx?ID=344
Note the code you need is actually this part :
cn.Open();
cn.InfoMessage += delegate(object sender, SqlInfoMessageEventArgs e)
{
txtMessages.Text += "\n" + e.Message;
};
It's the e.Message keeps returning the message back to txtMessages (You can replace as TextBox or Label).
You may also refer to this article:
Backup SQL Server Database with progress
An example of my code is in the following:
//The idea of the following code is to display the progress on a progressbar using the value returning from the SQL Server message.
//When done, it will show the final message on the textbox.
String connectionString = "Data Source=server;Integrated Security=SSPI;";
SqlConnection sqlConnection = new SqlConnection(connectionString);
public void DatabaseWork(SqlConnection con)
{
con.FireInfoMessageEventOnUserErrors = true;
//con.InfoMessage += OnInfoMessage;
con.Open();
con.InfoMessage += delegate(object sender, SqlInfoMessageEventArgs e)
{
//Use textBox due to textBox has Invoke function. You can also utilize other way.
this.textBox.Invoke(
(MethodInvoker)delegate()
{
int num1;
//Get the message from e.Message index 0 to the length of first ' '
bool res = int.TryParse(e.Message.Substring(0, e.Message.IndexOf(' ')), out num1);
//If the substring can convert to integer
if (res)
{
//keep updating progressbar
this.progressBar.Value = int.Parse(e.Message.Substring(0, e.Message.IndexOf(' ')));
}
else
{
//Check status from message
int succ;
succ = textBox.Text.IndexOf("successfully");
//or succ = e.Message.IndexOf("successfully"); //get result from e.Message directly
if (succ != -1) //If IndexOf find nothing, it will return -1
{
progressBar.Value = 100;
MessageBox.Show("Done!");
}
else
{
progressBar.Value = 0;
MessageBox.Show("Error, backup failed!");
}
}
}
);
};
using (var cmd = new SqlCommand(string.Format(
"Your SQL Script"//,
//QuoteIdentifier(databaseName),
//QuoteString(Filename)//,
//QuoteString(backupDescription),
//QuoteString(backupName)
), con))
{
//Set timeout = 1200 seconds (equal 20 minutes, you can set smaller value for shoter time out.
cmd.CommandTimeout = 1200;
cmd.ExecuteNonQuery();
}
con.Close();
//con.InfoMessage -= OnInfoMessage;
con.FireInfoMessageEventOnUserErrors = false;
}
In order to get the progressbar working, you need to implement this with a backgroundworker, which your application won't freeze and get 100% done suddenly.
The SqlConnection.InfoMessage event occurs when SQL Servers returns a warning or informational message. This website shows a possible implementation.
Related
Little bit of background of my Application.
I am working on a File Watcher windows service using C# that looks for .bak files in a particular folder and then use it to restore the database that file belongs to.
The Restored Database has a Stored Procedure that calls 10 different stored procedure. It's the File Watcher's functionality to execute the stored procedure after the Restore is done.
The Stored Procedure is [1_IMPORT_DATA_AND_PROCESS_ALL] which calls 10 different stored procedure within itself.
This is the Method which is executing the Stored Procedure after the restore is complete.
// Trigger Stored Procedure after restore.
private void triggerSP(String connectionStr)
{
// This doesn't open the Connection. conn.Open() has to be explicitly called.
SqlConnection conn = new SqlConnection(connectionStr);
try
{
conn.Open();
conn.FireInfoMessageEventOnUserErrors = true;
// Capture messages returned by SQL Server.
conn.InfoMessage += delegate (object sender, SqlInfoMessageEventArgs e)
{
message += " -> " + e.Message + " -> ";
};
//conn.InfoMessage += new SqlInfoMessageEventHandler(cn_InfoMessage);
//.create a command object identifying the stored procedure.
SqlCommand cmd = new SqlCommand("[dbo].[1_IMPORT_DATA_AND_PROCESS_ALL]", conn);
cmd.CommandTimeout = 0;
// 2. set the command object so it knows to execute a stored procedure.
cmd.CommandType = CommandType.StoredProcedure;
// Add a check here as well.
// execute the command.
SqlDataReader rdr = cmd.ExecuteReader();
string[] info = new string[] { "Message: \n" + message };
WriteToFile(info);
// Since we are not using - using block we have to explicitly call Close() to close the connection.
conn.Close();
}
catch (SqlException SqlEx){
string[] error = new string[3] ;
string msg1 = "Errors Count:" + SqlEx.Errors.Count;
string msg2 = null;
foreach (SqlError myError in SqlEx.Errors)
msg2 += myError.Number + " - " + myError.Message + "/" ;
conn.InfoMessage += delegate (object sender, SqlInfoMessageEventArgs e)
{
message += "\n" + e.Message;
};
error[0] = msg1;
error[1] = msg2;
error[2] = message;
WriteToFile(error);
}
finally
{
//call this if exception occurs or not
//in this example, dispose the WebClient
conn.Close();
}
}
Problem
I am only getting back the Message outputs from the very first stored procedure i.e [1_IMPORT_DATA_AND_PROCESS_ALL] and not from the stored procedure which are being called from within [1_IMPORT_DATA_AND_PROCESS_ALL] as shown bellow.
As soon as the 1st Sp calls another SP my codes stops reading the Messages.
I want to capture all the Messages that are being printed, something Like this (image below), which are the actual messages that are being printed when I execute the SP in SSMS.
This particular line is fetching the Messages from the SP
conn.InfoMessage += delegate (object sender, SqlInfoMessageEventArgs e)
{
message += " -> " + e.Message + " -> ";
};
So far I have referred to everything form this question and it's derivatives.
I can't change the Stored Procedure now, I can only make changes to my C# Application.
Hope my question is clear.
Good day,
Note! This message is not marked as "community wiki" and as such it written by a specific person under his name and this is not a shared article. If you have comments then please use the comments instead of changing the content, which the OP meant to provide (for example extra learning points in the content). Thanks!
In the following script I present an example of handling nested Stored Procedure error.The basic Idea is to use TRY/CATCH in order to prevent raising the error and stop the transaction, and the OUTPUT is used to return the error information to the upper level SP
This is only a basic example...
CREATE or ALTER PROCEDURE L1 (
#InputInt int,
#ErrMessage NVARCHAR(MAX) OUTPUT,
#ErrNum INT OUTPUT
)AS
SELECT ##NESTLEVEL AS 'Inner Level'; -- this information present the level of the SP during the execution. It is not needed for the solution but for the sake of the learning and understanding of nested SP
Select 'Start L1'
BEGIN TRY
-- When the ionput is 0 we Generate a divide-by-zero error.
SELECT 1/#InputInt;
END TRY
BEGIN CATCH
SET #ErrMessage = ERROR_MESSAGE()
SELECT #ErrMessage
END CATCH;
SET #ErrNum = ##ERROR
IF (#ErrNum > 0) Begin
SELECT 'L1 error Number: ' + CONVERT(NVARCHAR(10), #ErrNum)
Return
END
ELSE
select 'L1 OK'
GO
CREATE or ALTER PROCEDURE L2 (
#InputInt int
) AS
Declare #ErrMessage NVARCHAR(MAX) = '', #ErrNum INT = 0
SELECT ##NESTLEVEL AS 'Outer Level';
BEGIN TRY
EXEC L1 #InputInt, #ErrMessage, #ErrNum;
END TRY
BEGIN CATCH
SELECT 'There was error!'
select ##ERROR
END CATCH
GO
EXECUTE L2 1 -- OK
GO
EXECUTE L2 0; --Raise error in the nested stored procedures
GO
I'm attempting to add the finishing touch to a project I've been working on and am currently trying to modify a feature that I've created. The feature being that if a student has completed an examination, they are able to view the results. However, what I want to do is create an if else statement that is essentially: if the exam has been taken and completed, then they are redirected to the page that shows them the specific exam's results. Else, it returns a message at the top of the page stating "This examination has not been completed yet."
The current code I have (which is operated through a button on the page) is:
protected void btnViewPrevExam_Click(object sender, EventArgs e)
{
Session["intExaminationID"] = ddlExamination.SelectedValue;
Int32 int32StudentID = Convert.ToInt32(Session["StudentID"]);
Session["int32StudentID"] = Convert.ToInt32(int32StudentID);
// Define the ADO.NET connection object.
SqlConnection objSqlConnection = new SqlConnection(WebConfigurationManager.ConnectionStrings["OPT"].ConnectionString);
// Develop the SQL call.
// Develop the SQL call.
String strSQL = "";
strSQL = "SELECT AnswerID, Question, OptionA, OptionB, OptionC, OptionD, CorrectAnswer, Answer ";
strSQL += " FROM Question, Answer, Examination, Student ";
strSQL += " WHERE Examination.ExaminationID = " + ddlExamination.SelectedValue;
strSQL += " AND Student.StudentID = " + int32StudentID;
strSQL += " AND Answer.QuestionID = Question.QuestionID ";
strSQL += " AND Answer.StudentID = Student.StudentID ";
strSQL += " AND Examination.ExaminationID = Question.ExaminationID ";
// Create the SQL command object.
SqlCommand objSqlCommand = new SqlCommand(strSQL, objSqlConnection);
// Retrieve the row from the table.
objSqlConnection.Open();
SqlDataReader objSqlDataReader = objSqlCommand.ExecuteReader();
objSqlDataReader.Read();
if (strSQL != null)
{
objSqlDataReader.Close();
objSqlConnection.Close();
Response.Redirect("StudentExamResults.aspx");
}
else
{
this.Master.MessageForeColor = System.Drawing.Color.Red;
this.Master.Message = "The selected examination has not been completed.";
}
}
What this button does currently is that it will send the student to the examination results page regardless if the examination has been completed or not. This is due to the line "if (strSQL != null)" and it never being null because the SQL call has been made and filled. I've attempted other ideas, as well as performing a objSqlDataReader for the AnswerID but it didn't work properly. This is a small extra feature I'd like to add to this project that I thought of and would be very pleased if I could find some help on sorting out what I'm doing wrong. Thank you in advance!
Testing if strSQL is not null will always succeed because you are setting it to a non-null value earlier in the method.
To see if a record already exists for a previously-completed examination, you need to check the return value of the call to objSqlDataReader.Read(); it will return true as long as there are additional rows (or, in this case, a first row) to consume from your SELECT query. Thus, change this...
objSqlDataReader.Read();
if (strSQL != null)
{
...to this...
if (objSqlDataReader.Read())
{
As an additional note, consider wrapping objSqlConnection, objSqlCommand, and objSqlDataReader in using blocks to ensure they are properly closed/disposed. As it is now, you are not closing objSqlDataReader and objSqlConnection when the exam needs to be completed, and objSqlCommand is not disposed at all. objSqlDataReader would then be closed as follows, regardless of which branch of the if is taken...
using (SqlDataReader objSqlDataReader = objSqlCommand.ExecuteReader())
{
if (objSqlDataReader.Read())
{
//objSqlDataReader.Close();// No longer necessary - handled by using
objSqlConnection.Close();
Response.Redirect("StudentExamResults.aspx");
}
else
{
this.Master.MessageForeColor = System.Drawing.Color.Red;
this.Master.Message = "The selected examination has not been completed.";
}
}
Assuming you don't care about the contents, rather you just want to check if the row exists, you can do something like this:
string sql = "SELECT COUNT(AnswerID) FROM Question ........ WHERE ......";
using (var connection = CreateConnection()) {
using (var cmd = new SqlCommand(sql, connection)) {
bool exists = (int) cmd.ExecuteScalar() > 0;
if (exists) {
Response.Redirect("StudentExamResults.aspx");
} else {
// Do the other thing
}
}
}
I wrote a simple program to load a csv file using LOAD DATA LOCAL..
loadFileSQL = "LOAD DATA...."
using (MySqlCommand cmd = new MySqlCommand(loadFileSQL, conn))
{
try
{
Console.WriteLine("Loading File....");
cmd.ExecuteNonQuery();
Console.WriteLine("Load file complete...");
}
catch (MySqlException ex)
{
Trace.WriteLine("MySQL Something went wrong" + ex.InnerException.Message);
}
...
}
I would like to log the message how many rows were inserted, how many skipped and how many failed..How do i capture that ? If some failed, is it possible to know which rows failed?
Thanks all for your suggestions and tips.. I was able to get the infomessage print on the console by using the errors property
public static void OnInfoMessage(object sender, MySqlInfoMessageEventArgs e)
{
foreach (MySqlError err in e.errors)
{
Console.WriteLine(err.Code + ":" + err.Message);
}
}
and wherever you are opening a connection, just create the handler as mentioned in the comments
conn.InfoMessage += new MySqlInfoMessageEventHandler(OnInfoMessage);
While it does not print how many errored out or skipped or inserted, it prints outs any columns truncated or incorrect.. etc exactly as you see on your mysql command line.
Hi all and thanks in advance. I am trying to fix a method that inserts information to a database table. Currently its experiencing timeouts because its running in a while loop that is taking too long to process all the contents. While I know I could just increase the command timeout I don't think that solves the problem because I think its the code. But I'm not certain what the correct fix is. I have access to Dapper and I wonder if it would be more efficient to make a method that passes the necessary variables and executes just a quick simple statement for that group then goes to get the next one? Or is that just perpetuating what's below just in a different way? Should I move this out of the code and onto the server for better performance?
UPDATE Full error message:
Exception of type 'System.Web.HttpUnhandledException' was thrown.
File: c:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\6caa4c91\19b853c6\App_Web_o3102kpb.9.cs
Method: ProcessRequest Line Number : 0
Inner Exception: {Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
File: z:\inetpub\wwwroot\SessionTransfer.aspx.cs
Method: AddSessionToDatabase Line Number : 94
Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
File: z:\inetpub\wwwroot\SessionTransfer.aspx.cs Method: Page_Load Line Number : 33 }
Here is the original code:
SqlConnection con = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand();
con.Open();
cmd.Connection = con;
int i = 0;
string strSql, guid = GetGuid();
string temp = "";
while (i < Session.Contents.Count)
{
if (Session.Contents[i] == null)
temp = "";
else {
if ((Session.Contents[i].ToString().Trim().Length) > 0)
temp = Session.Contents[i].ToString().Replace("'", "''");
else
temp = "";
}
strSql = "INSERT INTO SessionTable (GUID, SessionKey, SessionValue) " +
"VALUES ('" + guid + "', '" + Session.Contents.Keys[i].ToString() + "', '" + temp + "')";
cmd.CommandText = strSql;
cmd.ExecuteNonQuery();
i++;
}
con.Close();
cmd.Dispose();
con.Dispose();
return guid;
UPDATE - FINAL SOLUTION:
var SessionList = new List<Session>();
while (i < Session.Contents.Count)
{
string temp = "";
if (Session.Contents[i] == null)
temp = "";
else
{
temp = (Session.Contents[i].ToString().Trim().Length) > 0 ? Session.Contents[i].ToString().Replace("'", "''") : "";
}
var s = new Session
{
TempGuid = guidTemp,
Contents = Session.Contents[i] != null ? Session.Contents[i].ToString() : null,
Temp = temp
};
SessionList.Add(s);
i++;
}
mySession = SerializationUtilities.SerializeObjectToXML(SessionList);
using (var con = new SqlConnection())
{
con.ExecuteHGW("Transfer", new { mySession }, commandType: CommandType.StoredProcedure);
}
Then on the SQL side I just put the XML in a table and did one single insert statement against the table, time is significantly improved.
I would like to suggest you 2 improvements:
in the loop you are executing insert statements one by one against db. It takes much time to open connection to db then send the query, execute it and return result. It is much better to batch them. So gather like 10,000 of such insert statements, build the whole instuction with StringBuilder and execute it against db in one go. This will really increase the speed of your app. Amount of Insert instructions in one batch you should choose yourself basing on system tests.
If after applying 1) hint the problem with timeout still occurs I would suggest 2 possible solutions:
a). do not send all elements to be processed against db to web service at once but instead, as previoulsy, batch them (apply second batching). So for instance send to web service 50,000 elements to be insterted, then wait for confirmation from web service and then proceed with next batch. The big advantage is that you can show to user easily progress bar showing him current operation state.
b). send all items to be processed against db at once but do not wait for result. In your app just show that items are processed and each 10 s send to web service request to ask if the job is finished. When it is finished signal it to user.
I have written following code.
I have opened the database connection for once for one query
I want to execute another query.
I have written the code below.
But i think there is a mistake
Can anyone help me please?
public void check()
{
try
{
OdbcConnection myOdbcConnection = new OdbcConnection(con1);
OdbcCommand myOdbcCommand = myOdbcConnection.CreateCommand();
String sSQL = "SELECT * FROM(select tdate from tbl_IThelpdesk order by call_no desc)where ROWNUM = 1"; //last record of the call_no column
myOdbcCommand.CommandText = sSQL;
myOdbcConnection.Open();
OdbcDataReader myOdbcDataReader = myOdbcCommand.ExecuteReader();
if (!myOdbcDataReader.Read())
{
txtDate.Text = DateTime.Now.ToShortDateString();
string strcallno = DateTime.Now.Year.ToString("d2") + DateTime.Now.Month.ToString("d2") + DateTime.Now.Day.ToString("d2");
txtcall.Text = "ITHD" + strcallno + "001";
myOdbcConnection.Close();
myOdbcDataReader.Close();
}
else
{
DateTime today = DateTime.Parse(DateTime.Now.ToShortDateString());
if (myOdbcDataReader[0].ToString() == today.ToString())
{
myOdbcConnection.Close();
myOdbcDataReader.Close();
myOdbcConnection.Open();
OdbcCommand myOdbcCommand1 = myOdbcConnection.CreateCommand();
String SQLmax = "SELECT max(call_no) FROM TBL_IThelpdesk";
myOdbcCommand1.CommandText = SQLmax;
OdbcDataReader myOdbcDataReader1 = myOdbcCommand1.ExecuteReader();
while (myOdbcDataReader1.Read() != false)
{
txtcall.Text = myOdbcDataReader1[0].ToString().Trim();
}
myOdbcDataReader1.Close();
myOdbcDataReader.Close();
myOdbcConnection.Close();
}
}
}
catch (Exception e)
{
lblEmpty.Text = e.Message;
lblEmpty.Visible = true;
}
}
Since database connections use a pool, you don't have to maintain the same connection for multiple queries; instead, open a connection when you need it, and close it as soon as possible to free up the resources.
See: http://msdn.microsoft.com/en-us/library/8xx3tyca(v=vs.80).aspx
See also: C# SQLConnection pooling
Note that you've not used using() { } pattern. Given that OdbcConnection and similar types implement IDisposable, you should embed them into a using in order for them to be disposed without waiting the garbage collector.
See: http://msdn.microsoft.com/en-us/library/yh598w02.aspx
You're hitting the database twice when you only need to hit it once. You're getting the latest date then going to the database again to get the corresponding call_no. This is unsafe as the max(call_no) could change even in the small amount of time between the 2 queries.
//get the latest [call_no] and [tdate]. No need for a 2cd trip with max(call_no)
SELECT * FROM(select call_no, tdate from tbl_IThelpdesk order by call_no desc)where ROWNUM = 1
Also the data access code is mixed with UI code. You should create data access methods that do 1 thing; return the data you want. This will make it much easier to follow the main flow of your algorithm.