I have a class that creates a database that saves the mdf file in a specified location. Then it copies tables from an existing database. Then creates stored procedures from an sql file. Then detaches the database created from the start once the process is done. My problem is that my detach method won't work throwing an exception saying that the database is in use. I have disposed my connections properly.
This is in-line with my previous question.
Here is my class:
Event
private void btnFullBackup_Click(object sender, EventArgs e)
{
progressBar.Value = 0;
lblStatus.Text = "Starting full backup...";
CreateDB("FULL");
progressBar.Value = 20;
lblStatus.Text = "Copying tables...";
CopyTables("FULL");
progressBar.Value = 60;
lblStatus.Text = "Creating stored procedures...";
CreateStoredProcedures("FULL");
progressBar.Value = 70;
progressBar.Value = 80;
DetachBackup("FULL");
lblStatus.Text = "Done";
progressBar.Value = 100;
MessageBox.Show("Backup was created successfully", "",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
Methods used:
void CreateDB(string type)
{
//define and browse location to save mdf
lblStatus.Text = "Creating pysical database...";
FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();
folderBrowserDialog.ShowDialog();
lblStatus.Text = "Checking folder permission...";
string selectedFolder = folderBrowserDialog.SelectedPath + "\\";
newBackupLocation = selectedFolder;
//check permission
if (WriteAccessToFolder(selectedFolder) == false)
{
MessageBox.Show("The folder you have chosen does not have write permission", "Monytron",
MessageBoxButtons.OK, MessageBoxIcon.Error);
folderBrowserDialog.ShowDialog();
return;
}
//create DB
lblStatus.Text = "Creating database...";
string connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
var query = GetDbCreationQuery(selectedFolder, type);
using (var conn = new SqlConnection(connectionString))
using (var command = new SqlCommand(query, conn))
{
try
{
conn.Open();
command.ExecuteNonQuery();
folderBrowserDialog.Dispose();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
if ((conn.State == ConnectionState.Open))
{
conn.Close();
}
}
}
}
void CopyTables(string backupDBName)
{
string connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
var query = CopyQuery(backupDBName + DateTime.Now.ToString("yyyyMMdd"));
using (var conn = new SqlConnection(connectionString))
using (var command = new SqlCommand(query, conn))
{
try
{
conn.Open();
command.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
if ((conn.State == ConnectionState.Open))
{
conn.Close();
}
}
}
}
void CreateStoredProcedures(string type)
{
string connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
using (var conn = new SqlConnection(connectionString + ";database=" + type + DateTime.Now.ToString("yyyyMMdd")))
{
string spLocation = File.ReadAllText("CreateStoredProcedures.sql");
Server server = new Server(new ServerConnection(conn));
try
{
server.ConnectionContext.ExecuteNonQuery(spLocation);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
bool DetachBackup(string backupDBName)
{
string connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
var builder = new SqlConnectionStringBuilder(connectionString);
string serverName = builder.DataSource;
string dbName = builder.InitialCatalog;
try
{
Server smoServer = new Server(serverName);
smoServer.DetachDatabase(backupDBName + DateTime.Now.ToString("yyyyMMdd"), false);
return true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
return false;
}
}
The Connection to the database is in most cases placed in a pool after using. This way you can re-connect quickly using the same connection string, but on the other hand, I suspect this pool of connections is blocking you from detaching a database.
You can probably do something like this:
Put use master as the last statement in each query against database before you close the connection, or
Modify connection string so it doesn't use pooling (uid=...; pwd=...; pooling=false;)
Hope it helps.
You should first kill connections to the database if you want to keep connection pooling. You can do it setting the database in single user access with rollback_immediate clause before calling the detach method.
Have a look here to use C#:
Is there a way to set the DB as Single User Mode in C#?
Or here to run T-SQL script:
https://serverfault.com/questions/76432/how-can-i-detach-a-database-that-is-in-use
Related
I'm trying to restore a SQL-Server database but I don't know why it's throwing exception which message is "Restore failed for server '.'".
I'm executing two methods. One is defines as CheckDatabase and second one is RestoreDatabase. If I execute RestoreDatabase method first it works fine, but if I execute it after CheckDatabase method it explodes
This is my CheckDatabase():
private static void CheckDatabase(string period)
{
string connString = Config.ConnectionStrings.ConnectionStrings["connString"].ConnectionString;
string controlProcesoCommand = "select top 5 * from ControlProceso order by FechaInicio desc;";
using (SqlConnection conn = new SqlConnection(connString))
using (SqlCommand command = new SqlCommand(controlProcesoCommand, conn))
{
try
{
conn.Open();
var reader = command.ExecuteReader();
while (reader.Read())
{
int lastPeriodDb = int.Parse(reader["Periodo"].ToString());
int actualPeriod = int.Parse(period);
if (period.Equals(reader["Periodo"].ToString()))
throw new Exception(Resources.Messages.Period_already_processed);
else if (lastPeriodDb > actualPeriod)
throw new Exception(Resources.Messages.Period_saved_after_actual_period);
else break;
}
reader.Close();
}
catch (Exception ex)
{
throw ex;
}
finally
{
conn.Close();
conn.Dispose();
command.Cancel();
command.Dispose();
}
}
}
And this is my RestoreDatabase():
private static void RestoreDatabase()
{
try
{
SqlConnection conn = new SqlConnection(Config.ConnectionStrings.ConnectionStrings["connString"].ConnectionString);
string dbName = conn.Database;
string restoreFilePath = Config.AppSettings.Settings["RestoreFilePath"].Value;
Server myServer = new Server(conn.DataSource);
Database itauDatabase = new Database(myServer, dbName);
Restore dbRestore = new Restore();
dbRestore.Action = RestoreActionType.Database;
dbRestore.Database = itauDatabase.Name;
dbRestore.Devices.AddDevice(restoreFilePath, DeviceType.File);
dbRestore.ReplaceDatabase = true;
dbRestore.SqlRestore(myServer);
}
catch (Exception ex)
{
throw ex;
}
}
Each method works fine when I execute them separated.
Thanks!
I have a piece of code that going to the database every minute to check if there is any report that need to be run, and if there is any it runs it.
The issue is that my object initiation to a database class creates memory leak. If I look at task mgr "user Object" grown by 5 every time tick executing a code.
private void ReportRunTimer_Tick(object sender, EventArgs e)
{
DataConnection dataConnection = new DataConnection(); *<-- when executing this line User Object increasing.*
try
{
reportsToRun = dataConnection.GetListOfTheReportForReportRunTick();
if (reportsToRun.Count > 0)
foreach (string report in reportsToRun)
{
logs("Starting Automatic report generatin", "Successful");
Thread TicketReportMethodThread = new Thread(() => GenerateReport(report, 1));
TicketReportMethodThread.Start();
}
dataConnection = null;
} catch (Exception ex)
{
logs("Starting Automatic report generatin failed: " +ex.ToString(), "Error");
}
finally
{
reportsToRun.Clear();
}
}
DataConnection class
public List<string> GetListOfTheReportForReportRunTick()
{
List<string> RepoerList = new List<string>();
connString = "Server=xxx;Port=xxx;Database=xxx;Uid=xxx;password=xxxx;SslMode=none";
using (MySqlConnection mySqlConnection = new MySqlConnection(connString))
{
try
{
MySqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText = "SELECT reportname FROM reports WHERE nextruntime < NOW()";
mySqlConnection.Open();
MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();
while (mySqlDataReader.Read())
{
RepoerList.Add(mySqlDataReader["reportname"].ToString());
}
}
catch (MySqlException ex)
{
hd.logs("Failed to get reports with reportstorun_tick Error: " + ex.ToString(), "Error");
mySqlConnection.Close();
}
finally
{
mySqlConnection.Close();
mySqlConnection.Dispose();
}
}
return RepoerList;
}
DataConnection dataConnection = new DataConnection(); used in a few more places and this is the only one that causing an issue.
If I replace code in private void ReportRunTimer_Tick with code from public List GetListOfTheReportForReportRunTick() like bellow. Issue no longer exist, any ideas?
private void ReportRunTimer_Tick(object sender, EventArgs e)
{
List<string> reportsToRun = new List<string>();
try
{
connString = "Server=xxx;Port=xxx;Database=xxx;Uid=xxx;password=xxxx;SslMode=none";
using (MySqlConnection mySqlConnection = new MySqlConnection(connString))
{
try
{
MySqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText = "SELECT reportname FROM reports WHERE nextruntime < NOW()";
mySqlConnection.Open();
MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();
while (mySqlDataReader.Read())
{
reportsToRun.Add(mySqlDataReader["reportname"].ToString());
}
if (reportsToRun.Count > 0)
foreach (string report in reportsToRun)
{
logs("Starting Automatic report generatin", "Successful");
Thread TicketReportMethodThread = new Thread(() => GenerateReport(report, 1));
TicketReportMethodThread.Start();
}
}
catch (MySqlException ex)
{
logs("Failed to get reports with reportstorun_tick Error: " + ex.ToString(), "Error");
mySqlConnection.Close();
}
finally
{
mySqlConnection.Close();
mySqlConnection.Dispose();
}
}
}
catch (Exception ex)
{
logs("Starting Automatic report generatin failed: " + ex.ToString(), "Error");
}
finally
{
reportsToRun.Clear();
}
}
Issue is caused by
DataConnection dataConnection = new DataConnection(); *<-- when executing this line User Object increasing.*
reportsToRun = dataConnection.GetListOfTheReportForReportRunTick();
But I can't understand why.
I think the memory leak is because you're not disposing your MySqlDataReader. You can do this by just wrapping it in a using statement like this:
using(MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader()){
while (mySqlDataReader.Read())
{
RepoerList.Add(mySqlDataReader["reportname"].ToString());
}
}
I have a form that checks whether values are in a database before adding them. Each field is in a different table, and to keep everything clean, I have a checkExists method for each field. Is there a way to have a separate method that connects to the database, so that I don't have to connect in every field method?
I'd like to do something like this so that my code is less messy:
public void SetConnection()
{
SqlConnection myConnection =
new SqlConnection("user id=[username];" +
"password=[password];" +
"server=[server];" +
"database=[db_name];");
try
{
myConnection.Open();
}
catch(Exception e)
{
Console.WriteLine("Unable to Connect");
}
}
public Boolean CheckData_Company(string[] items)
{
Class_DB set_conn = new Class_DB();
try
{
set_conn.SetConnection();
}
catch(Exception e)
{
Console.WriteLine(e.ToString());
}
//check that item does not already exist
string query_string = "SELECT * FROM CR_Company WHERE ([CompanyName] = #companyName";
SqlCommand check_Company = new SqlCommand(query_string, set_conn);
check_Company.Parameters.AddWithValue("#CompanyName", items[0]);
int CompanyExist = (int)check_Company.ExecuteScalar();
if(CompanyExist > 0)
{
return true;
}
else
{
return false;
}
}
But I get a
local variable set_conn
Argument 2: Cannot Convert from Class_DB to System.Data.SqlClient.SqlConnection
I understand the error, so what can I do to return the correct value, or do I have to establish a connection within my CheckData_Comany() method?
Your method SetConnection should be returning SqlConnection back like:
public SqlConnection SetConnection()
{
SqlConnection myConnection = new SqlConnection("user id=[username];" +
"password=[password];" +
"server=[server];" +
"database=[db_name];");
try
{
myConnection.Open();
}
catch(Exception e)
{
Console.WriteLine("Unable to Connect");
}
return myConnection;
}
and then you can have something like:
SqlConnection connection = set_conn.SetConnection();
and then pass it in SqlCommand constructor as parameter :
SqlCommand check_Company = new SqlCommand(query_string, connection);
Your complete method implementation would become :
public Boolean CheckData_Company(string[] items)
{
bool Exists = false;
Class_DB set_conn = new Class_DB();
SqlConnection connection = null;
try
{
connection = set_conn.SetConnection();
//check that item does not already exist
string query_string = "SELECT * FROM CR_Company WHERE ([CompanyName] = #companyName";
SqlCommand check_Company = new SqlCommand(query_string, set_conn);
check_Company.Parameters.AddWithValue("#CompanyName", items[0]);
int CompanyExist = (int)check_Company.ExecuteScalar();
if(CompanyExist > 0)
Exists = true;
}
catch(Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
connection.Close();
}
return Exists;
}
and important thing to note is do not forget the close the connection finally by calling connection.Close(), otherwise it might cause eating up the resources that shouldn't happen when we are done with querying the database and we should release the resources occupied.
I have an difficult situation :
this is my form :
the first button '...' is a btnAllegato. Code :
private void btnAllegato_Click(object sender, EventArgs e)
{
try
{
using (OpenFileDialog openFileDialog1 = new OpenFileDialog())
{
string path = string.Empty;
openFileDialog1.Title = "Seleziona richiestaIT (PDF)..";
openFileDialog1.Filter = ("PDF (.pdf)|*.pdf");
openFileDialog1.FilterIndex = 1;
openFileDialog1.FileName = "";
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
//salva l'intero path
path = openFileDialog1.FileName;
//nome file + estensione
string temp = openFileDialog1.SafeFileName;
//elimina l'estensione del file con IgnoreCase -> case Unsensitive
temp = Regex.Replace(temp, ".pdf", " ", RegexOptions.IgnoreCase);
//datatime + replace
string timenow = System.DateTime.Now.ToString();
//replace data da gg//mm/aaaa ss:mm:hh -----> ad gg-mm-aaaa_ss-mm-hh
timenow = timenow.Replace(":", "-").Replace("/", "-");//.Replace(" ", " ");
_url = #"\\192.168.5.7\dati\SGI\GESTIONE IT\RichiesteIT\" + temp + timenow + ".pdf";
System.IO.File.Copy(path, _url);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
after i have a button Inserisci >> (btnInserisci)
with this button i Create a DB Query to insert data...
private void btnInserisci_Click(object sender, EventArgs e)
{
try
{
if ((_IDRichiedente != -1) && (_data != string.Empty) && (_url != string.Empty))
{
MessageBox.Show(_url);
QueryAssist qa = new QueryAssist();
string query = "INSERT INTO RICHIESTA_IT(ID_Risorsa, descrizione_richiesta, modulo_pdf, data_richiesta) VALUES('" + _IDRichiedente + "', '" + txtBreveDescrizione.Text + "', '" + _url + "', '" + _data + "');";
MessageBox.Show(query);
qa.runQuery(query);
else
{
MessageBox.Show("Selezionare il richiedente,data o allegato!");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
where
private int _IDRichiedente = -1;
private string _data = String.Empty;
private string _url = string.Empty;
is a fields of class.
QueryAssist is my class that connect, run query and disconnect to Access DB.
code :
class QueryAssist
{
System.Data.OleDb.OleDbConnection _OleDBconnection;
public QueryAssist()
{
this._OleDBconnection = null;
}
private bool connectionDB()
{
string connection = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=\"\\\\192.168.5.7\\dati\\Scambio\\Sviluppo\\Impostazioni temporanea db Censimento\\CensimentoIT.accdb\"";
try
{
_OleDBconnection = new System.Data.OleDb.OleDbConnection(connection);
_OleDBconnection.Open();
return true;
}
catch(Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
return false;
}
}
private void disconnectDB()
{
try
{
_OleDBconnection.Close();
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
public System.Data.DataTable runQuery(string query)
{
try
{
if (connectionDB())
{
System.Data.DataTable dataTable = new System.Data.DataTable();
System.Data.OleDb.OleDbCommand sqlQuery = new System.Data.OleDb.OleDbCommand(query, _OleDBconnection);
System.Data.OleDb.OleDbDataAdapter adapter = new OleDbDataAdapter(sqlQuery);
adapter.Fill(dataTable);
disconnectDB();
return dataTable;
}
}
catch(Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
return null;
}
public int countRowsQueryResult(string query)
{
try
{
if (connectionDB())
{
System.Data.DataTable dataTable = new System.Data.DataTable();
System.Data.OleDb.OleDbCommand sqlQuery = new System.Data.OleDb.OleDbCommand(query, _OleDBconnection);
System.Data.OleDb.OleDbDataAdapter adapter = new OleDbDataAdapter(sqlQuery);
adapter.Fill(dataTable);
disconnectDB();
return dataTable.Rows.Count;
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
return -1;
}
}
At firt time ... The application work good. I selected a file and other data and I click on button 'Inserisci>>' and all working good.
Next step when i want to insert other data ... when i click on '...' button for attachment a file i have the loop OpenFileDialog
To close, i must kill the process.
I have [STAThread] set on main of the program.
Connect to NAS isn't a problem ... I have try in local .. and i have the same problem..
If i click on btn '...' to OpenFileDialg then not click on button 'Inserisci>>'
OpenFileDialog work good for all time ...
But if i click on button 'Inserisci>>' on the next click on button '...' to OpenFileDialog application loop..
Sorry for bad english ..I'm here for clarification
The use of the runQuery method with an INSERT statement could be the cause of your problems. To insert a record you should use an OleDbCommand with the ExecuteNonQuery. A Fill method is used to fill a DataTable.
The fact that the record is inserted anyway happens because the underlying command used to fill the DataTable (ExecuteReader) ignores its sql command text and executes what you have passed. However after that the Fill method expects to fill a DataTable and not having a select statement could be potentially the cause of your problems.
I would use a different method when you need to Update/Delete or Insert new data
public int runNonQuery(string query)
{
try
{
if (connectionDB())
{
OleDbCommand sqlQuery = new OleDbCommand(query, _OleDBconnection);
int rows = sqlQuery.ExecuteNonQuery();
disconnectDB();
return rows;
}
}
catch(Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
return -1;
}
}
There are other problems in your code and are all connected to the way in which you concatenate together the string to form an sql statement. This is know as the worst practice possible with database code. If you take a bit of your time to investigate how to write a parameterized query you will avoid a lot of future problems.
I'm browsing a .MDF file and uploading its data to a SQL Server database table.
My app has a browse button to select the file and then upload button to perform the bulkcopy operation.
But in second attempt when I choose the .mdf file using OpenFileDialog, it throws an error saying the file is already being used.
private void labelBrowse_Click(object sender, System.EventArgs e)
{
try
{
System.Windows.Forms.OpenFileDialog openFileDialog = new System.Windows.Forms.OpenFileDialog();
openFileDialog.Title = "Select the MDF file to upload";
openFileDialog.Filter = "Data|*.mdf";
if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
textBoxMdfFilePath.Text = openFileDialog.FileName.ToString();
}
catch (System.Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.ToString());
}
}
private void labelUpload_Click(object sender, System.EventArgs e)
{
if (string.IsNullOrEmpty(textBoxMdfFilePath.Text.Trim()))
{
System.Windows.Forms.MessageBox.Show("Please select a MDF file to upload");
textBoxMdfFilePath.Text = "";
return;
}
else if (!(System.IO.Path.GetFileName(textBoxMdfFilePath.Text).Equals("Audit.mdf", System.StringComparison.InvariantCultureIgnoreCase)) && !(System.IO.Path.GetFileName(textBoxMdfFilePath.Text).Equals("IPAUDIT.mdf", System.StringComparison.InvariantCultureIgnoreCase)))
{
System.Windows.Forms.MessageBox.Show("Please select the correct MDF file to upload");
textBoxMdfFilePath.Text = "";
return;
}
else
{
uploadToServer(textBoxMdfFilePath.Text);
}
}
void uploadToServer(string path)
{
try
{
string mdfConnectionString = #"data source=172.16.2.136;attachdbfilename=" + path + ";" + "integrated security=true;" + "connect timeout=30;" + "user instance=true";
System.Data.SqlClient.SqlConnection sqlconnection = new System.Data.SqlClient.SqlConnection(mdfConnectionString);
if (System.IO.Path.GetFileName(path).Equals("Audit.mdf", System.StringComparison.InvariantCultureIgnoreCase))
{
System.Data.SqlClient.SqlCommand sqlcommand = new System.Data.SqlClient.SqlCommand("select * from [D-Audit]", sqlconnection);
sqlconnection.Open();
System.Data.DataTable dataTable = new System.Data.DataTable();
dataTable.Load(sqlcommand.ExecuteReader());
sqlconnection.Close();
System.Data.SqlClient.SqlBulkCopy sqlbulkcopy = new System.Data.SqlClient.SqlBulkCopy(System.Configuration.ConfigurationManager.ConnectionStrings["myServerAudit"].ConnectionString);
sqlbulkcopy.DestinationTableName = "[dbo].[D-Audit]";
sqlbulkcopy.BulkCopyTimeout = 1800;
sqlbulkcopy.WriteToServer(dataTable);
sqlbulkcopy.Close();
System.Windows.Forms.MessageBox.Show("Data Uploaded Successfully");
textBoxMdfFilePath.Text = "";
}
else if (System.IO.Path.GetFileName(path).Equals("IPAudit.mdf", System.StringComparison.InvariantCultureIgnoreCase))
{
System.Data.SqlClient.SqlCommand sqlcommand = new System.Data.SqlClient.SqlCommand("select * from IpTransaction", sqlconnection);
sqlconnection.Open();
System.Data.DataTable dataTable = new System.Data.DataTable();
dataTable.Load(sqlcommand.ExecuteReader());
sqlconnection.Close();
System.Data.SqlClient.SqlBulkCopy sqlbulkcopy = new System.Data.SqlClient.SqlBulkCopy(System.Configuration.ConfigurationManager.ConnectionStrings["myServerIPAudit"].ConnectionString);
sqlbulkcopy.DestinationTableName = "IpTransaction";
sqlbulkcopy.BulkCopyTimeout = 1800;
sqlbulkcopy.WriteToServer(dataTable);
sqlbulkcopy.Close();
System.Windows.Forms.MessageBox.Show("Data Uploaded Successfully");
textBoxMdfFilePath.Text = "";
}
sqlconnection.Close();
}
catch (System.Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
You need to set Pooling=False in your connection string
Whats happening here is SQLBulkcopy is creating its own SQLConnection object with connection string you provided. By default, the Connection Pool is true...so when sqlconnection object is created by SQLBulkCopy, it is added in connection pool and connection persists even after application is closed.This mechanism is to optimize performance but in your case that's what is causing issue...so adding Pooling=false in your connection should solve your problem
ConnectionString = "Data Source=MYSQL;Initial Catalog=MyDatabase;Integrated Security=true;Pooling=false"
Additinal Info
It is good practice to wrap your SQLBulkCopy with Using block as it is of type IDisposable so you need not call close explicitly.
When you say on "second attempt", I'm assuming it works on first click but not on subsequent clicks. If that's the case, then the connection is probably not being released and still holding on to the DB. You may try disposing all IDisposables in your code before leaving the event handler.