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.
Related
I have read the numerous posts on why you should give the using statement preference over manually doing .Open() then .Close() and finally .Dispose().
When I initially wrote my code, I had something like this:
private static void doIt(string strConnectionString, string strUsername)
{
SqlConnection conn = new SqlConnection(strConnectionString);
try
{
conn.Open();
string strSqlCommandText = $"CREATE USER {strUsername} for LOGIN {strUsername} WITH DEFAULT SCHEMA = [dbo];";
SqlCommand sqlCommand = new SqlCommand(strSqlCommandText, conn);
var sqlNonReader = sqlCommand.ExecuteNonQuery();
if (sqlNonReader == -1) Utility.Notify($"User Added: {strUsername}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
conn.Close();
conn.Dispose();
}
}
and this works... no problem. but only ONCE.
so, if I do something like this:
private static void doItLots(string strConnectionString, string strUsername)
{
for(int i=0; i<10; i++)
{
doIt(strConnectionString, $"{strUsername}_{i}");
}
}
it works the FIRST time when i=0, but any subsequent iterations fail with Cannot open database "myDbName" requested by the login. The login failed.
However, if I go back and comment out the conn.Dispose(); line, then it works fine on all iterations.
The problem is simply that if I want to do the .Dispose() part outside of the method, then I am forced to pass a SqlConnection object instead of simply passing the credentials, potentially making my code a bit less portable and then I need to keep the connection around longer as well. I was always under the impression that you want to open and close connections quickly but clearly I'm misunderstanding the way the .Dispose() command works.
As I stated at the outset, I also tried doing this with using like this...
private static void doIt(string strConnectionString, string strUsername)
{
using (SqlConnection conn = new SqlConnection(strConnectionString))
{
try
{
conn.Open();
string strSqlCommandText = $"CREATE USER {strUsername} for LOGIN {strUsername} WITH DEFAULT SCHEMA = [dbo];";
SqlCommand sqlCommand = new SqlCommand(strSqlCommandText, conn);
var sqlNonReader = sqlCommand.ExecuteNonQuery();
if (sqlNonReader == -1) Utility.Notify($"User Added: {strUsername}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
conn.Close();
}
}
}
and this does the exact same thing as the initial code with .Dispose() called manually.
Any help here would be greatly appreciated. I'd love to convert to the using statements but having trouble figuring out how to write reusable methods that way...
UPDATE:
I have narrowed it down a bit. The issue is NOT the iterations or making the calls over-and-over again. But I am still getting an access error. Here is the code:
string strConnectionString = $#"Data Source={StrSqlServerDataSource};Initial Catalog={StrDatabaseName};User id={StrSqlServerMasterUser};Password={StrSqlServerMasterPassword}";
using (SqlConnection connUserDb = new SqlConnection(strConnectionString))
{
try
{
Utility.Notify($"Connection State: {connUserDb.State.ToString()}"); // Responds as 'Closed'
connUserDb.Open(); // <-- throws error
Utility.Notify($"Connection State: {connUserDb.State.ToString()}");
Utility.Notify($"MSSQL Connection Open... Adding User '{strUsername}' to Database: '{strDatabaseName}'");
string sqlCommandText =
//$#"USE {StrDatabaseName}; " +
$#"CREATE USER [{strUsername}] FOR LOGIN [{strUsername}] WITH DEFAULT_SCHEMA = [dbo]; " +
$#"ALTER ROLE [db_datareader] ADD MEMBER [{strUsername}]; " +
$#"ALTER ROLE [db_datawriter] ADD MEMBER [{strUsername}]; " +
$#"ALTER ROLE [db_ddladmin] ADD MEMBER [{strUsername}]; ";
using (SqlCommand sqlCommand = new SqlCommand(sqlCommandText, connUserDb))
{
var sqlNonReader = sqlCommand.ExecuteNonQuery();
if (sqlNonReader == -1) Utility.Notify($"User Added: {strUsername} ({sqlNonReader})");
}
result = true;
}
catch (Exception ex)
{
Utility.Notify($"Creating User and Updating Roles Failed: {ex.Message}", Priority.High);
}
finally
{
connUserDb.Close();
Utility.Notify($"MSSQL Connection Closed");
}
}
return result;
}
The error I am getting here is: Cannot open database requested by the login. The login failed.
One clue I have is that prior to this, I was running this same code with two changes:
1) uncommented the USE statement in the sqlCommandText
2) connected to the Master database instead
When I did that, it didn't work either, and instead I got this error: The server principal is not able to access the database under the current security context.
If I go into SSMS and review the MasterUser they are listed as db_owner and I can perform any activities I want, including running the command included in the code above.
I rewrote all the code to make use of a single connection per the recommendations here. After running into the "server principal" error, I added one more connection to attempt to directly connect to this database rather than the master.
UPDATE 2:
Here is another plot twist...
This is working from my local computer fine (now). But, not (always) working when run from an Azure Webjob that targets an Amazon Web Services (AWS) Relational Database Server (RDS) running MSSQL.
I will have to audit the git commits tomorrow, but as of 5p today, it was working on BOTH local and Azure. After the last update, I was able to test local and get it to work, but when run on Azure Webjob it failed as outlined above.
SqlConnection implements IDisposable. You don't call dispose or close.
try{
using (SqlConnection conn = new SqlConnection(strConnectionString))
{
conn.Open();
string strSqlCommandText = $"CREATE USER {strUsername} for LOGIN {strUsername} WITH DEFAULT SCHEMA = [dbo];";
SqlCommand sqlCommand = new SqlCommand(strSqlCommandText, conn);
var sqlNonReader = sqlCommand.ExecuteNonQuery();
if (sqlNonReader == -1) Utility.Notify($"User Added: {strUsername}");
}}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
I write a C# program which uses a database, Matches.mdf.
I want to test the database file existence, using File.Exists routine (codes will come at the end of the question). If the file doesn't exist, the program creates a new database with the above name. To test the database existence routine, I renamed the database file, but when I wanted to create the database, I got the following error message: Database "Matches" already exists, please specify a different name.
At a second test, I used a database dropping routine before calling the creating routine. Big mistake. Every time I try to create the Matches.mdf database, I get the following error message:
I am sure that the cause of this error message is me, tinkering around, because the same database creation and deletion routines worked fine before.
I know I can solve the problem by changing the path of the database file, but I want to know what exactly I broke up here so I know for next time.
What I am asking is: what can I do to solve the above error?
Later edit: I tried to manually recreate the Matches.mdf using the query tool from SQL Server Object Explorer from VS 2019. Worked perfectly, but I don't think it's a good solution long term.
Necessary codes:
Variable declarations:
static readonly string DatabaseFolder = Path.GetDirectoryName(Application.ExecutablePath) + "\\db";
readonly string DatabaseFile = DatabaseFolder + "\\Matches.mdf";
readonly string DatabaseLog = DatabaseFolder + "\\MatchesLog.ldf";
The function that checks the database file existence:
public bool DatabaseExists()
{
return File.Exists(DatabaseFile);
}
The database creation routine:
private bool CreateDatabaseFile()
{
SqlConnection MyConn = new SqlConnection(CreateDatabaseConnectionString);
string Str = "Create Database Matches on Primary (Name=Matches, Filename='#DatabaseFile') log on (Name=MatchesLog, Filename='#DatabaseLog')";
SqlCommand DatabaseCreationCommand = new SqlCommand(Str, MyConn);
DatabaseCreationCommand.Parameters.Add("#DatabaseFile", SqlDbType.Text).Value = DatabaseFile;
DatabaseCreationCommand.Parameters.Add("#DatabaseLog", SqlDbType.Text).Value = DatabaseLog;
try
{
MyConn.Open();
DatabaseCreationCommand.ExecuteNonQuery();
}
catch (SqlException S)
{
MessageBox.Show(S.Message);
return false;
}
catch (IOException I)
{
MessageBox.Show(I.Message);
return false;
}
catch (InvalidOperationException I)
{
MessageBox.Show(I.Message);
return false;
}
catch (InvalidCastException I)
{
MessageBox.Show(I.Message);
return false;
}
finally
{
MyConn.Close();
}
return true;
}
The database deleting routine:
public void DeleteDatabase()
{
string Str;
SqlConnection MyConn = new SqlConnection(CreateDatabaseConnectionString);
Str = "Alter database Matches set single_user with rollback immediate\r\ndrop database Matches";
SqlCommand command = new SqlCommand(Str, MyConn);
try
{
MyConn.Open();
command.ExecuteNonQuery();
}
catch (SqlException S)
{
MessageBox.Show(S.Message);
}
catch (IOException I)
{
MessageBox.Show(I.Message);
}
catch (InvalidOperationException I)
{
MessageBox.Show(I.Message);
}
catch (InvalidCastException I)
{
MessageBox.Show(I.Message);
}
finally
{
MyConn.Close();
}
}
As it is said here and confirmed by Jeroen Mostert, Create database does not accept queries. The database was created before, using some string concatenation. Afterwards the query string was parametrized, without realizing that this command doesn't take parameters. This is why changing the creating database query to
Create Database Matches
works perfectly.
Well, live and learn!
I tried to find a useful answer to this question, but failed (the closest I found was this). I have a C# app calling a stored procedure which uses SQL TRY/CATCH to handle errors. I can replicate the issue with a sample stored procedure like this:
if object_id('dbo.TestSQLError') is not null drop proc dbo.TestSQLError
go
create proc dbo.TestSQLError
as
begin try
select 1 / 0
end try
begin catch
raiserror('Bad tings appen mon', 16, 1)
end catch
then a little dummy program like this:
namespace TestSQLError
{
class Program
{
public const string CONNECTION_STRING = #"data source=localhost\koala; initial catalog=test; integrated security=true;";
static void Main(string[] args)
{
try
{
using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
{
SqlCommand cmd = new SqlCommand("dbo.TestSQLError", conn) { CommandType = System.Data.CommandType.StoredProcedure };
conn.Open();
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
Console.WriteLine(rdr.GetValue(0).ToString());
}
rdr.Close();
}
conn.Close();
}
Console.WriteLine("Everything looks good...");
}
catch (SqlException se)
{
Console.WriteLine("SQL Error: " + se.Message);
throw se;
}
catch (Exception e)
{
Console.WriteLine("Normal Error: " + e.Message);
throw e;
}
Console.ReadLine();
}
}
}
The stored procedure raises an error of level 16 which as far as I've read should be enough to be error-worthy. However control never jumps to the catch block; it just chugs through like nothing went wrong.
I read someone suggest using OUTPUT parameters... which I can do, but it seems like I must be missing something fundamental and simple here. Can anyone help?
UPDATE: It would appear if I use ExecuteNonQuery() the errors propagate just fine. However my use case is a procedure which performs DML and returns data based on that DML. Maybe the answer is "don't do that" but it'd be nice to know if there's a way to simply catch an error when grabbing results.
The reason is because the raise error is after the end of the first result set in the data reader and we’ll only get the error if we call NextResult() on the data reader!
When using a SqlDataReader it will only iterate over the first result
set, which in this case will be the select in the stored procedure
Try and see more details here
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (!rdr.IsClosed())
{
while (rdr.Read())
{
Console.WriteLine(rdr.GetValue(0).ToString());
}
if (!rdr.NextResult())
{
rdr.Close();
}
}
}
I can make three suggestions:
Use CommandType.Text instead of CommandType.StoredProcedure. This may be fixed now, but a number of years ago I found that CommandType.StoredProcedure would always buffer message outputs into batches of about 50, while CommandType.Text would allow the messages to come back to C# right away.
Add a WITH NOWAIT hint to your RAISERROR code.
Don't forget about the FireInfoMessageEventOnUserErrors property. You do want to handle the InfoMessage event.
I'm not sure any of these will solve your problem, but they should give you some direction.
I am Beginner for Development and i am creating exe file to execute the SQL scripts. I have 3 doubts here.
1. How to get the file name from the folder at run time. (Currently i did hardcore)
2. How to write a log file after executed the query.
3. How to show alert message after executed the query? (Patch executed successful / Patch execution failed)
Please find my script mentioned below;
var txtconn = File.ReadAllText(#"C:\Users\Patch\Connstrng.txt");
var txtfile = File.ReadAllText(#"C:Users\Patch\Script.txt");
string[] sqlqry = txtfile.Split(new[] {"~GO~"}, StringSplitOptions.RemoveEmptyEntries);
var conn = new SqlConnection(txtconn);
var cmd = new SqlCommand("query", conn);
conn.Open();
foreach (var query in sqlqry){
cmd.CommandText = query;
cmd.ExecuteNonQuery();}
conn.Close();
// Need to show alert message as "Patch executed successful. Please send Result Log to Support
// Need to create the result log file.'
You're going to want to use a try catch block
foreach (var query in sqlqry)
{
try
{
cmd.CommandText = query;
cmd.ExecuteNonQuery();
string logPath = ""; //Path for log
using (StreamWriter writer = new StreamWriter(logPath, true))
{
writer.WriteLine("Ran query " + query); //W
}
System.Windows.Forms.MessageBox.Show("Patch executed successful. Please send Result Log to Support // Need to create the result log file.");
}
catch (Exception ex)
{
//Handle Error
}
}
You'll need to set a path for where you want to write your log file. After executing the query, the StreamWriter class will write whatever you want to that log file. (The true parameter indicates that you will append to this log file, so each entry doesn't overwrite the last). Then you can use a MessageBox to display the information.
Because the executing of the query is ran inside of a try block, if it fails (some sort of exception happens) it will enter the catch block, in which case you will need to handle that error, perhaps by popping up another message box or logging something to the file.
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.