C# how to check if database is not busy? - c#

My class has a couple of methods going on. The first one is creating a database, that's done. Then, creates stored procedures that is being read from a sql file. then detach that DB. Now it seems that my store procedure query is taking a while to finish and my method to detach is being invoked while the database is busy. So how do I tell if the database is idle. The exception goes "cannot detach the database because it is currently in use"
Methods:
void CreateStoredProcedures(string type)
{
string spLocation = File.ReadAllText("CreateStoredProcedures.sql");
var conn = new SqlConnection(connectionString + ";database=" + type + DateTime.Now.ToString("yyyyMMdd"));
try
{
Server server = new Server(new ServerConnection(conn));
server.ConnectionContext.ExecuteNonQuery(spLocation);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
bool DetachBackup(string type)
{
var conn = new SqlConnection(connectionString);
SqlCommand command = new SqlCommand("", conn);
command.CommandText = #"sys.sp_detach_db '" + type + DateTime.Now.ToString("yyyyMMdd") + "'";
try
{
conn.Open();
command.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
return false;
}
finally
{
if ((conn.State == ConnectionState.Open))
{
conn.Close();
}
}
return true;
}
Click event:
private void btnFullBackup_Click(object sender, EventArgs e)
{
lblStatus.Text = "Starting full backup...";
Execute("FULL");
progressBar.Value = 20;
lblStatus.Text = "Copying tables...";
progressBar.Value = 60;
CopyTables("FULL");
progressBar.Value = 70;
lblStatus.Text = "Creating stored procedures...";
CreateStoredProcedures("FULL");
progressBar.Value = 80;
CheckDBSize(newBackupLocation, "FULL");
progressBar.Value = 100;
MessageBox.Show("Backup was created successfully", "",
MessageBoxButtons.OK, MessageBoxIcon.Information);
lblStatus.Text = "Done";
progressBar.Value = 0;
if (DetachBackup("FULL") == false)
{
DetachBackup("FULL");
}
}

Chances are it's getting hung on its own connection. sp_detach_db's MSDN https://msdn.microsoft.com/en-CA/library/ms188031.aspx has the following suggestion under the section Obtain Exclusive Access:
USE master;
ALTER DATABASE [DBName] SET SINGLE_USER;
You're DetachBackup method will have connect to master, run the ALTER and the sp_detach_db procedure.

You aren't closing the connection in your CreateStoredProcedures method. Put using statements in like I've shown here and it should fix the problem. (Brief using statement explanation from Microsoft.)
Try this code for your methods:
void CreateStoredProcedures(string type)
{
string spLocation = File.ReadAllText("CreateStoredProcedures.sql");
using (var conn = new SqlConnection(connectionString + ";database=" + type + DateTime.Now.ToString("yyyyMMdd")))
{
try
{
Server server = new Server(new ServerConnection(conn));
server.ConnectionContext.ExecuteNonQuery(spLocation);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
} // End of using, connection will always close when you reach this point.
}
bool DetachBackup(string type)
{
using (var conn = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand(#"sys.sp_detach_db '" + type + DateTime.Now.ToString("yyyyMMdd") + "'", conn);
try
{
conn.Open();
command.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
return false;
}
} // End of using, connection will always close when you reach this point.
return true;
}

You shouldn't think of it as the database being "busy", but the error message uses good verbage: in use. To find out if the database is currently in use, the most accurate way would be to find out if any sessions have any lock in the particular database, by querying sys.dm_tran_locks. Here is a helper function to return a bool whether or not the database is in use:
bool IsDatabaseInUse(string databaseName)
{
using (SqlConnection sqlConnection = new SqlConnection("... your connection string ..."))
using (SqlCommand sqlCmd = new SqlCommand())
{
sqlCmd.Connection = sqlConnection;
sqlCmd.CommandText =
#"select count(*)
from sys.dm_tran_locks
where resource_database_id = db_id(#database_name);";
sqlCmd.Parameters.Add(new SqlParameter("#database_name", SqlDbType.NVarChar, 128)
{
Value = databaseName
});
sqlConnection.Open();
int sessionCount = Convert.ToInt32(sqlCmd.ExecuteScalar());
if (sessionCount > 0)
return true;
else
return false;
}
}
Note: Make sure your initial catalog in your connection string isn't the database you're trying to make "not in use", as that'll put your current session in the context of the database, not allowing that operation to complete

Related

How to create database if not exist in c# Winforms

I want to create a database if it does not exist. I am trying to do it with this code but it has errors and I get this message
enter image description here
Please help.
Code:
if(dbex == false)
{
string str;
SqlConnection mycon = new SqlConnection("Server=.\\sqlexpress;initial catalog=Masalehforoshi;Integrated security=SSPI;database=master");
str = "CREATE DATABASE [Masalehforoshi] CONTAINMENT = NONE ON PRIMARY" +
"(NAME=N'Masalehforoshi'," +
#"FILENAME=N'C:\data\Masalehforoshi.mdf' " +
",SIZE=3072KB,MAXSIZE=UNLIMITED,FILEGROWTH=1024KB)" +
"LOG ON (NAME=N'Masalehforoshi_log.', " +
#"FILENAME=N'C:\Masalehforoshi_log.ldf' "+
",SIZE=1024KB,MAXSIZE=2048GB,FILEGROWTH=10%)";
SqlCommand mycommand = new SqlCommand(str, mycon);
try
{
mycommand.Connection.Open();
mycommand.ExecuteNonQuery();
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString(), "myprogram", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
finally
{
if(mycon.State == ConnectionState.Open)
{
mycon.Close();
}
}
}
My Create Database function
public bool CreateDatabase(SqlConnection connection, string txtDatabase)
{
String CreateDatabase;
string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
GrantAccess(appPath); //Need to assign the permission for current application to allow create database on server (if you are in domain).
bool IsExits = CheckDatabaseExists(connection, txtDatabase); //Check database exists in sql server.
if (!IsExits)
{
CreateDatabase = "CREATE DATABASE " + txtDatabase + " ; ";
SqlCommand command = new SqlCommand(CreateDatabase, connection);
try
{
connection.Open();
command.ExecuteNonQuery();
}
catch (System.Exception ex)
{
MessageBox.Show("Please Check Server and Database name.Server and Database name are incorrect .", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
return false;
}
finally
{
if (connection.State == ConnectionState.Open)
{
connection.Close();
}
}
return true;
}
return false;
}
My GrantAccess function to allow permission for current app
public static bool GrantAccess(string fullPath)
{
DirectoryInfo info = new DirectoryInfo(fullPath);
WindowsIdentity self = System.Security.Principal.WindowsIdentity.GetCurrent();
DirectorySecurity ds = info.GetAccessControl();
ds.AddAccessRule(new FileSystemAccessRule(self.Name,
FileSystemRights.FullControl,
InheritanceFlags.ObjectInherit |
InheritanceFlags.ContainerInherit,
PropagationFlags.None,
AccessControlType.Allow));
info.SetAccessControl(ds);
return true;
}
Check Database exists function below
public static bool CheckDatabaseExists(SqlConnection tmpConn, string databaseName)
{
string sqlCreateDBQuery;
bool result = false;
try
{
sqlCreateDBQuery = string.Format("SELECT database_id FROM sys.databases WHERE Name = '{0}'", databaseName);
using (SqlCommand sqlCmd = new SqlCommand(sqlCreateDBQuery, tmpConn))
{
tmpConn.Open();
object resultObj = sqlCmd.ExecuteScalar();
int databaseID = 0;
if (resultObj != null)
{
int.TryParse(resultObj.ToString(), out databaseID);
}
tmpConn.Close();
result = (databaseID > 0);
}
}
catch (Exception)
{
result = false;
}
return result;
}
Based on this support article https://support.microsoft.com/en-us/kb/307283 which has a similar database creation script I suggest removing the "CONTAINMENT = NONE" section.
By default, all SQL Server 2012 and later databases have a containment set to NONE.(https://msdn.microsoft.com/en-us/library/ff929071.aspx), so it probably isn't necessary for your script
It is possible that ado .net doesn't support that tsql command, there is a whole other SQL Server Management Objects library available for messing with advance database and schema scripts https://msdn.microsoft.com/en-us/library/ms162169.aspx . I've used it to create missing databases with table definitions etc during application startup.
To simplify things, here is an even shorter solution.
public void CreateDatabaseIfNotExists(string connectionString, string dbName)
{
SqlCommand cmd = null;
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
using (cmd = new SqlCommand($"If(db_id(N'{dbName}') IS NULL) CREATE DATABASE [{dbName}]", connection))
{
cmd.ExecuteNonQuery();
}
}
}

Copying tables from one database to another on same server

As the server requires a connection for each database, the answers I've found in SO don't work at all.
For some tables I have to make some calculations before copying the rows, but some I can copy whole table. And finally it's an automation in my program that I've wrote.
Oldcn is the connection to old database, Newcn for the new database.
For the tables that I can copy whole I wrote this procedure below.
Is there a better an short way to do this job? (It works on background)
private string[] CopyTva(MySqlConnection Oldcn, MySqlConnection Newcn, string[] res, DoWorkEventArgs we,string msg)
{
int counter = int.Parse(res[1]);
MySqlCommand cmd = new MySqlCommand("SELECT * FROM tvaval", Oldcn);
MySqlCommand cmd1 = new MySqlCommand("INSERT INTO tvaval (id,tvavalue,cr_user,cr_date,up_user,up_date) VALUES (#id,#tvavalue,#cr_user,#cr_date,#up_user,#up_date)", Newcn);
MySqlDataReader rd = null;
try
{
rd = cmd.ExecuteReader();
while (rd.Read())
{
cmd1.Parameters.AddWithValue("#id", rd["id"].ToString());
//bla bla same as above
try
{
cmd1.ExecuteNonQuery();
}
catch (MySqlException e)
{
rd.Dispose();
res[2] = "Erreur:TVA " + e.Message.ToString();
return res;
}
++counter;
bgw.ReportProgress((counter * 100) / DbTotalRow,msg);
cmd1.Parameters.Clear();
}
rd.Dispose();
}
catch (MySqlException e)
{
res[2] = "Erreur:TVA " + e.Message.ToString();
return res;
}
res[0] = "1";res[1] = counter.ToString();res[2] = "";
return res;
}
MySQL doesn't require a separate connection for each database. A connection has a default database, but you're allowed to specify the database name explicitly before the table name, and this will override the default. So you can do this with a single query:
INSERT INTO newdb.tvaval
SELECT * FROM olddb.tvaval

Write into 2 table in same DB in 2 different background worker process

I have a winforms app which will write data into 2 different tables in same DB. My timer will count every one second. When the first machine is ready to give data, I will get it and write to the first table. When the machine 2 is ready as well, I will get it and write data to the second table. I put these two inserting processes in two different background worker processes. But I keep getting an error "ExecuteNonQuery requires an open and available Connection. The connection's current state is connecting." Below is my code.
private void timer1_Tick(object sender, EventArgs e)
{
readMachinewidth();
}
private void readMachinewidth()
{
if(M1 == "true")
{
backgroundWorker1.RunWorkerAsync();
}
if(M2 == "true")
{
backgroundWorker2.RunWorkerAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (!oData.saveM1ProcessQty("M1", "M1"))
{
MessageBox.Show("M1 - Database Error");
return;
}
}
private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
{
if (oData.saveM2ProcessQty("M2", "M2"))
{
MessageBox.Show("M2 - Database Error");
return;
}
}
May I know how I can fix it? How I can make the winforms can write data in two different tables in almost the same time in same database. I expect it is the connection open issue. But I can't figure out how to fix it. When write table 1 data the connection is open while at the same time the data for Machine 2 may be ready to write as well.
My data insertion function
public Boolean saveM1ProcessQty(string M1, string M1a)
{
try
{
string sSQL = "";
SqlCommand oCmd;
sSQL = "INSERT INTO xxx(M1, M1-1)VALUES('";
sSQL = sSQL + M1+ "','" + M1a+ "')";
oConn.ConnectionLocal.Open();
oCmd = new SqlCommand(sSQL, oConn.ConnectionLocal);
oCmd.ExecuteNonQuery();
oConn.ConnectionLocal.Close();
oCmd.Dispose();
return true;
}
catch(SqlException sqlex)
{
oConn.ConnectionLocal.Close();
Common.ErrorLog("M1 - " + sqlex.Message.ToString());
return false;
}
catch(Exception ex)
{
oConn.ConnectionLocal.Close();
Common.ErrorLog("M1 - " + ex.Message.ToString());
return false;
}
}
public Boolean saveM2ProcessQty(string M2, string M2a)
{
try
{
string sSQL = "";
SqlCommand oCmd;
sSQL = "INSERT INTO xxx(M1, M1-1)VALUES('";
sSQL = sSQL + M2+ "','" + M2a+ "')";
oConn.ConnectionLocal.Open();
oCmd = new SqlCommand(sSQL, oConn.ConnectionLocal);
oCmd.ExecuteNonQuery();
oConn.ConnectionLocal.Close();
oCmd.Dispose();
return true;
}
catch(SqlException sqlex)
{
oConn.ConnectionLocal.Close();
Common.ErrorLog("M2 - " + sqlex.Message.ToString());
return false;
}
catch(Exception ex)
{
oConn.ConnectionLocal.Close();
Common.ErrorLog("M2 - " + ex.Message.ToString());
return false;
}
}
My Connection class
public SqlConnection ConnectionLocal
{
get
{
if(_localConn == null)
{
string sconnstring =
ConfigurationManager.ConnectionStrings["local"].ToString();
_localConn = new SqlConnection(sconnstring);
}
return _localConn;
}
}
That error occurs when you are not calling SqlConnection.Open(). It has nothing to do with concurrency. Without seeing your actual database access code, I can't help you much more than that. But somewhere along the line, the connection is being created in memory but not connected.
Edit:
In re to a comment, here's the code with using blocks as appropriate. I also took the liberty of parameterizing your query to avoid SQL injection attacks.
public Boolean saveM1ProcessQty(string M1, string M1a)
{
try
{
string sSQL = "INSERT INTO xxx (M1, M1-1) VALUES ('#m1, #m1a')";
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["local"].ToString()))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(sSQL, conn));
{
cmd.Parameters.AddWithValue("#m1", M1);
cmd.Parameters.AddWithValue("#m1a", M1a);
cmd.ExecuteNonQuery();
}
}
return true;
}
catch(Exception ex)
{
Common.ErrorLog("M1 - " + ex.Message.ToString());
return false;
}
}

Unable to Rollback Transaction: This SqlTransaction has completed

I have the following code for copying data from one server to a different server:
private static string CopyData(string sourceConnection, string targetConnection, bool push = true)
{
string result = "Copy started";
SqlConnection source = new SqlConnection(sourceConnection);
SqlConnection target = new SqlConnection(targetConnection);
SqlTransaction targetTransaction;
source.Open();
target.Open();
if (source.State != ConnectionState.Open || target.State != ConnectionState.Open)
{
throw new Exception("Unable to connect to server at this time.");
}
targetTransaction = target.BeginTransaction();
try
{
ClearTable(target, targetTransaction, "TableAAA");
ClearTable(target, targetTransaction, "TableBBB");
CopyTable(source, target, targetTransaction, "TableAAA");
CopyTable(source, target, targetTransaction, "TableBBB");
targetTransaction.Commit();
result = "Copy successful";
}
catch (Exception E)
{
targetTransaction.Rollback();
result = "An SQL Error has occurred. Unable to copy data at this time.\n\n" + E.Message;
}
finally
{
target.Close();
source.Close();
}
return result;
}
private static void ClearTable(SqlConnection destination, SqlTransaction tran, string table)
{
SqlCommand cmd = new SqlCommand(string.Format("DELETE FROM {0}", table), destination);
cmd.Transaction = tran;
cmd.ExecuteNonQuery();
}
private static void CopyTable(SqlConnection source, SqlConnection destination, SqlTransaction tran, string table)
{
SqlCommand cmd = new SqlCommand(string.Format("DELETE FROM {0}", table), destination);
cmd.Transaction = tran;
cmd.ExecuteNonQuery();
cmd = new SqlCommand(string.Format("SELECT * FROM {0}", table), source);
SqlDataReader reader = cmd.ExecuteReader();
SqlBulkCopy bulkData = new SqlBulkCopy(destination, SqlBulkCopyOptions.Default, tran);
bulkData.DestinationTableName = table;
bulkData.BulkCopyTimeout = (int)Properties.Settings.Default.CommandTimeOut;
bulkData.WriteToServer(reader);
bulkData.Close();
reader.Close();
}
If I force an error by changing the schema of one of the tables, I get the error "This SqlTransaction has completed" when it attempts to rollback any changes. How do I correct this problem and why is it happening?
I'm not sure of the exact problem you're having, but I would recommend you rewrite your code in such a way that it uses the using statement. This would prevent you from needed to explicitly close your connections or rollback your transactions.
private static string CopyData(string sourceConnection, string targetConnection, bool push = true)
{
using (var source = new SqlConnection(sourceConnection))
using (var target = new SqlConnection(targetConnection))
{
source.Open();
target.Open();
// no need to check for open status, as Open will throw an exception if it fails
using (var transaction = target.BeginTransaction())
{
// do work
// no need to rollback if exception occurs
transaction.Commit();
}
// no need to close connections explicitly
}
}

Parallel reading from SQL Server

I was looking through dozens of articles, but couldn't find a solution.
Here is the logic :
I have a Winform (VS2010) application, that needs to read data from SQL Server 2008 R2 Express table A, process some calculations and store in a different table B.
I want to use parallel ForEach in order to shorten execution time (otherwise the calculation + SQL process takes days.....)
I have to read from SQL, because the database has over 5 million rows, each read returns a few hundreds rows.
Lists are defined as :
BindingList<ItemsClass> etqM = new BindingList<ItemsClass>();
BindingList<ItemsClass> etqC = new BindingList<ItemsClass>();
The parallel execution :
Parallel.ForEach(etqC, cv => {
readData(ref etqM, "tableA", "WHERE ID LIKE '" + cv.Name + "%'");
IList<ItemsClass> eResults = etqM.OrderBy(f => f.ID).ToList();
foreach (ItemsClass R in eResults)
{
//calculations comes here
etqM[rID] = R;
}
Parallel.ForEach(etqM, r => {
// part 2 of calculations comes here
}
});
exportList(etqM, "tableB", true);
});
The SQL Read function :
The function gets a List, Table name + conditions for the SQL
read from SQL the records, and transform them to the List format.
public void readData<T>(ref BindingList<T> etqList, string tableName, string conditions)
{
SqlConnection myConnection = new SqlConnection();
SqlCommand myCommand = new SqlCommand();
myCommand.CommandTimeout = 0;
myCommand.Connection = myConnection;
etqList.Clear();
openConn(myConnection);
SqlDataReader myReader = null;
try
{
int totalResults;
myCommand.CommandText = "SELECT COUNT (*) FROM " + tableName + " " + conditions;
totalResults = (int)myCommand.ExecuteScalar();
if (totalResults > 0)
{
myCommand.CommandText = "SELECT * FROM " + tableName + " " + conditions;
myReader = myCommand.ExecuteReader();
etqList = ConvertTo<T>(convertReaderToDT(myReader));
}
}
catch (Exception ex) { }
finally
{
try { myReader.Close(); }
catch { }
}
closeConn(myConnection);
}
The SQL export function : this function exports the given list to the table name.
private void exportListToSql<T>(IEnumerable<T> etqList, string tableName)
{
SqlConnection myConnection = new SqlConnection();
SqlCommand myCommand = new SqlCommand();
myCommand.CommandTimeout = 0;
myCommand.Connection = myConnection;
openConn(myConnection);
try
{
actionTotalCount++;
DataTable dt = new DataTable(tableName);
dt = ToDataTable(etqList);//List Name
var bulkCopy = new SqlBulkCopy(myConnection,
SqlBulkCopyOptions.TableLock |
SqlBulkCopyOptions.FireTriggers |
SqlBulkCopyOptions.UseInternalTransaction,
null
);
bulkCopy.DestinationTableName = tableName;
bulkCopy.BatchSize = BATCH_SIZE;
bulkCopy.WriteToServer(dt);
}
catch (Exception ex) { }
finally { closeConn(myConnection); }
}
SQL openConn and closeConn :
void openConn(SqlConnection myConnection)
{
if (myConnection.State == ConnectionState.Open) return;
myConnection.ConnectionString = "Data Source=" + DB_NAME + ";Initial Catalog=APPDB;Integrated Security=True;Connect Timeout=120;Asynchronous Processing=true;";
try { myConnection.Open(); actionTotalCount++; }
catch (System.Exception ex) { MessageBox.Show(ex.Message); }
}
void closeConn(SqlConnection myConnection)
{
if (myConnection.State == ConnectionState.Fetching || myConnection.State == ConnectionState.Executing || myConnection.State == ConnectionState.Connecting) return;
try { myConnection.Dispose(); }
catch (System.Exception ex) { MessageBox.Show(ex.Message); }
}
The problem is : once I execute, I get this message :
ExecuteScalar requires an open and available connection. The connection's current state is closed.
This message arrives for all threads, except the first one.
Any ideas ?
Apparently the problem was not in the SQL, but the calculations.
Since the List was defined outside the 'Parallel.Foreach', it was accesses by different threads simultaneously, causing a crash.
Once I changed the code as followed, all worked excellent :
Parallel.ForEach(etqC, cv => {
BindingList<ItemsClass> etqM = new BindingList<ItemsClass>();
readData(ref etqM, "tableA", "WHERE ID LIKE '" + cv.Name + "%'");
IList<ItemsClass> eResults = etqM.OrderBy(f => f.ID).ToList();
foreach (ItemsClass R in eResults)
{
//calculations comes here
etqM[rID] = R;
}
Parallel.ForEach(etqM, r => {
// part 2 of calculations comes here
}
});
exportList(etqM, "tableB", true);
});

Categories

Resources