How to backup SQL localDB with SMO - c#

I am trying to backup a SQL localDB database using SMO but with no success.
What is wrong with my code?
progressBar.Value = 0;
SaveFileDialog sfd = new SaveFileDialog();
string stringCon = #"Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\whdb.mdf;Integrated Security=True";
sfd.Filter = "Database backup files (*.bak)|*.bak";
sfd.Title = "Create Database Backup";
sfd.FileName = DateTime.Today.ToString("ddMMMyyyy") + ".bak";
if (sfd.ShowDialog() == DialogResult.OK)
{
using (SqlConnection conn = new SqlConnection(stringCon))
{
ServerConnection servConn = new ServerConnection(conn);
SqlConnection.ClearAllPools();
conn.Open();
servConn.Connect();
try {
Server serverdb = new Server(servConn);
Backup backupdb = new Backup() { Action = BackupActionType.Database, Database="whdb"};
backupdb.Devices.AddDevice(sfd.FileName, DeviceType.File);
backupdb.Initialize = true;
backupdb.Incremental = false;
backupdb.SqlBackupAsync(serverdb);
progressBar.Value = 100;
conn.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
This the exception I get when I run it:
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Data.dll
Additional information: ExecuteNonQuery requires an open and available Connection. The connection's current state is closed.

I think the problem is that you are using
backupdb.SqlBackupAsync(serverdb);
which starts the backup running in the background. But, by the time the backup gets around to running, you have closed your connection.
You could either use
backupdb.SqlBackup(serverdb);
or else remove your using and close and find some was to manually close the connection after the backup.

Related

AccessViolationException thrown by OleDbConnection.Open()

I am getting a System.AccessViolationException error when trying to use the .Open() method on my OleDbConnection variable. But, confoundingly, it doesn't seem to happen when I run my code on a different computer.
My code is for a service that I want running on a server. It appears to work correctly when I run it on my personal computer, but it throws the AccessViolationException error when I try running it on the server.
Code Versions:
I am writing in C# using Visual Studios 2019 on Windows 10
OleDbConnection is from "Assembly System.Data, Version=4.0.0.0"
ADOX is from "Assembly Interop.ADOX, Version=2.8.0.0", System.Runtime.InteropServices
ADODB is from "Assembly Interop.ADODB, Version=2.8.0.0", System.Runtime.InteropServices
My code:
internal static bool CreateMDB(MySchemaClass schema, string filePath)
{
OleDbConnection conn = null;
bool status = true;
string connectionString = string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0}", filePath);
try
{
// Setup new file
catalog.Create(connectionString);
((ADODB.Connection)catalog.ActiveConnection).Close();
conn = new OleDbConnection(connectionString);
conn.Open(); // <-- Error occurs here
// Write to new file
WriteDataToFile(schema, conn);
}
catch (Exception ex)
{
status = false;
}
finally
{
if (conn != null)
conn.Close();
}
return status;
}
StackTrace:
The best lead I have found so far is this post, but I'm a bit hazy about how to proceed from that starting point.
To programmatically create an Access database do the following:
Add reference to Microsoft ADO Ext. 6.0 for DDL and Security
In VS menu, click Project
Select Add Reference
Click COM
Check Microsoft ADO Ext. 6.0 for DDL and Security
CreateAccessDatabase
public static string CreateAccessDatabase(string fullyQualifiedAccessFilename, string dbPassword = "")
{
string connectionString = String.Format(#"Provider = Microsoft.ACE.OLEDB.12.0; Data Source = {0}", fullyQualifiedAccessFilename);
if (String.IsNullOrEmpty(fullyQualifiedAccessFilename))
{
throw new Exception("Error (CreateAccessDatabase) - Database filename is null or empty.");
}
if (!String.IsNullOrEmpty(dbPassword))
{
connectionString = String.Format(#"Provider = Microsoft.ACE.OLEDB.12.0; Data Source = {0};Jet OLEDB:Database Password='{1}'", fullyQualifiedAccessFilename, dbPassword);
}
//create new instance
ADOX.Catalog cat = new ADOX.Catalog();
//create Access database
cat.Create(connectionString);
//close connection
cat.ActiveConnection.Close();
//release COM object
System.Runtime.InteropServices.Marshal.ReleaseComObject(cat);
GC.Collect();
cat = null;
return String.Format("Database created '{0}'", fullyQualifiedAccessFilename);
}
Usage:
string result = string.Empty;
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "Access Database 2000-2003 (*.mdb)|*.mdb|Access Database 2007 (*.accdb)|*.accdb";
if (sfd.ShowDialog() == DialogResult.OK)
{
//create Access database
result = CreateAccessDatabase(sfd.FileName);
}
To programmatically create a table in an Access database:
Add using statement:
using System.Data.OleDb;
CreateTableProduct:
public static int CreateTableProduct(string fullyQualifiedAccessFilename, string dbPassword = "")
{
int result = 0;
string connectionString = String.Format(#"Provider = Microsoft.ACE.OLEDB.12.0; Data Source = {0}", fullyQualifiedAccessFilename);
if (!String.IsNullOrEmpty(dbPassword))
{
connectionString = String.Format(#"Provider = Microsoft.ACE.OLEDB.12.0; Data Source = {0};Jet OLEDB:Database Password='{1}'", fullyQualifiedAccessFilename, dbPassword);
}
string sqlText = string.Empty;
sqlText = "CREATE TABLE Product ";
sqlText += "(ID AUTOINCREMENT not null primary key,";
sqlText += " Name varchar(50) not null,";
sqlText += " Price currency, Quantity integer);";
using (OleDbConnection con = new OleDbConnection(connectionString))
{
//open connection
con.Open();
using (OleDbCommand sqlCmd = new OleDbCommand(sqlText, con))
{
//execute command
result = sqlCmd.ExecuteNonQuery();
}
}
return result;
}
Resources:
ADO Features for each Release
Which Access file format should I use?
CREATE TABLE statement (Microsoft Access SQL)
System.Data.OleDb Namespace
The underwhelming answer to the problem is that my server was lacking some of the files my personal computer had, and only needed the correct files installed.
The missing piece in this case was the Microsoft Access Database Engine 2016 Redistributable, which I ended up finding here. Running that executable on my server got the needed files and everything worked after that.

Reason for file lock while bulkcopy its data to table

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.

C# Unable to detach database

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

How to backup - restore .mdf database

I have developed a windows application with vs2010 and c#. I would like to know a way to backup and restore my local mdf database programmatically. With sdf database I use File Copy but it doesn't seem to work with mdf files.
Can anyone help?
Try in this way:
Go to Sql Management Studio and select the database you want to
backup
Right click and select 'Tasks' -> 'Backup'
Adjust the parameters as you like, but don't confirm the dialog
Press the button SCRIPT and dismiss the dialog
On the query window insert the following text before the backup
command
CREATE PROCEDURE DO_BACKUP
AS
BEGIN
-- HERE GOES THE BACKUP TEXT CREATED BY THE SCRIPT BUTTON
-- FOR EXAMPLE
BACKUP DATABASE [Customers]
TO DISK = N'E:\backups\customers.bak'
WITH NOFORMAT, NOINIT,
NAME = N'Customers - Full Database Backup',
SKIP, NOREWIND, NOUNLOAD, STATS = 10
END
and execute (selecting the correct database) using the exclamation mark button.
Now you have a stored procedure called DO_BACKUP that you can call from your code using the normal ADO.NET objects like SqlConnection and SqlCommand
I struggled a lot with this, and the accepted answer does not do the trick, so here is a solution that worked for me (thanks to dnxit)
it may help someone.
Backup
try
{
var dlg = new System.Windows.Forms.FolderBrowserDialog();
var result = dlg.ShowDialog(this.GetIWin32Window());
if (result.ToString() == "OK")
{
var dbfileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "LibraryManger.mdf");
var backupConn = new SqlConnection { ConnectionString = eb.GetConnectionString() };
backupConn.Open();
var backupcomm = backupConn.CreateCommand();
var backupdb = $#"BACKUP DATABASE ""{dbfileName}"" TO DISK='{Path.Combine(dlg.SelectedPath,"LibraryManagement.bak")}'";
var backupcreatecomm = new SqlCommand(backupdb, backupConn);
backupcreatecomm.ExecuteNonQuery();
backupConn.Close();
MessageBox.Show($"Database backup has successfully stored in {Path.Combine(dlg.SelectedPath, "LibraryManagement.bak")}", "Confirmation");
}
}
catch (Exception ex)
{
if(ex.Message.Contains("Operating system error"))
{
MessageBox.Show("Please chose a public folder.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
else
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
Restore
You'll have to close existing connection before you restore
try
{
if (eb != null)
{
eb.DisposeConnection();
eb = null;
}
var dlg = new OpenFileDialog();
dlg.InitialDirectory = "C:\\";
dlg.Filter = "Database file (*.bak)|*.bak";
dlg.RestoreDirectory = true;
if (Equals(dlg.ShowDialog(), true))
{
using (var con = new SqlConnection())
{
con.ConnectionString = #"Data Source=(LocalDB)\MSSQLLocalDB;Database=Master;Integrated Security=True;Connect Timeout=30;";
con.Open();
var dbfileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "LibraryManger.mdf");
using (var cmd = new SqlCommand())
{
cmd.Connection = con;
cmd.CommandText = $#"RESTORE DATABASE ""{dbfileName}"" FROM DISK='{dlg.FileName}'";
cmd.ExecuteNonQuery();
}
con.Close();
}
MessageBox.Show($"Database backup has successfully restored.", "Confirmation");
eb = new EntityBroker.EntityBroker();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}

Restoring SQL Server 2005 database using C#. Rollback issue

Hi I'm just starting to learn C#. I am trying to restore a .bak file. However I am getting the error. Exclusive access cannot be obtained because the database is in use.
I did my research here and here both says I have to perform a rollback. I do not know how to apply rollback in my restore code.
public void RestoreDatabase(String RestorePath)
{
try
{
SqlConnection sqlCon = new SqlConnection("Data Source=RITZEL-PC\\SQLEXPRESS;User ID=NNIT-Admin;Password=password;Initial Catalog=master;");
ServerConnection connection = new ServerConnection(sqlCon);
Server sqlServer = new Server(connection);
Restore restoreDB = new Restore();
restoreDB.Database = "NNIT DB";
restoreDB.Action = RestoreActionType.Database;
restoreDB.Devices.AddDevice(RestorePath, DeviceType.File);
restoreDB.ReplaceDatabase = true; // will overwrite any existing DB
restoreDB.NoRecovery = false; // NoRecovery = true;
restoreDB.SqlRestore(sqlServer);
MessageBox.Show("Restored");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + " " + ex.InnerException);
}
}
Using SMO you can set user access and rollback like this:
Server sqlServer = new Server(connection);
Database db = sqlServer.Databases["DbToRestore"];
if (db != null)
{
sqlServer.KillAllProcesses(db.Name);
db.DatabaseOptions.UserAccess = DatabaseUserAccess.Multiple;
db.Alter(TerminationClause.RollbackTransactionsImmediately);
}
Restore restoreDB = new Restore();
Does this work?
SqlCommand cmd = new SqlCommand("ALTER DATABASE yourdatabasename SET MULTI_USER WITH ROLLBACK IMMEDIATE", sqlConn);
cmd.ExecuteNonQuery();

Categories