WPF - How to Backup / Restore LocalDB Programmatically - ClickOnce - c#

I have an application which uses EF and LocalDB as it's database, published by ClickOnce.
it's my first time using LocalDB and I don't know how can i add a feature to my application to Backup/Restore The Database Programmatically.
My App Path Installed by ClickOnce :
C:\Users\Mahdi Rashidi\AppData\Local\Apps\2.0\NOL11TLW.9XG\CZM702AQ.LPP\basu..tion_939730333fb6fcc8_0001.0002_fd707bbb3c97f8d3
and This is the location which Database files Installed :
C:\Users\Mahdi Rashidi\AppData\Local\Apps\2.0\NOL11TLW.9XG\CZM702AQ.LPP\basu...exe_939730333fb6fcc8_0001.0002_none_8c555c3966727e7f
How Should I Backup/Restore the Database?
How Can I Keep Database Safe from ClickOnce further Updates?
Thanks alot :)

This is what I did for backup and restore of my localDb
public void BackupDatabase(string filePath)
{
using (TVend2014Entities dbEntities = new TVend2014Entities(BaseData.ConnectionString))
{
string backupQuery = #"BACKUP DATABASE ""{0}"" TO DISK = N'{1}'";
backupQuery = string.Format(backupQuery, "full databsase file path like C:\tempDb.mdf", filePath);
dbEntities.Database.SqlQuery<object>(backupQuery).ToList().FirstOrDefault();
}
}
public void RestoreDatabase(string filePath)
{
using (TVend2014Entities dbEntities = new TVend2014Entities(BaseData.ConnectionString))
{
string restoreQuery = #"USE [Master];
ALTER DATABASE ""{0}"" SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
RESTORE DATABASE ""{0}"" FROM DISK='{1}' WITH REPLACE;
ALTER DATABASE ""{0}"" SET MULTI_USER;";
restoreQuery = string.Format(restoreQuery, "full db file path", filePath);
var list = dbEntities.Database.SqlQuery<object>(restoreQuery).ToList();
var resut = list.FirstOrDefault();
}
}
Hope this is what you want.

I had a bugger of a time getting my backup/restore to work from code in my application. I'm using LOCALDB and wanted to make sure that regardless of the state of the database or the location of the .mdf file that the backup and restore functions would work. After all - the DBMS should take care of that for you. In the end this is how I got my backup and restore functions to work:
Note: code in VB - save the ";" :)
Backup:
Dim cbdfilename As String = controlPath & "\Backup\Temp\cbdb.bak"
Dim connString As String = (server + ";Initial Catalog=master;Integrated Security=True;")
Dim conn As New SqlConnection(connString)
Dim sql As String
sql = "Backup database #DBNAME " _
& " to Disk = #FILENAME" _
& " with Format"
SqlConnection.ClearAllPools()
'execute backup
Dim dbcmd As New SqlCommand(sql, conn)
dbcmd.Parameters.AddWithValue("#DBNAME", database)
dbcmd.Parameters.AddWithValue("#FILENAME", cbdfilename)
conn.Open()
Try
dbcmd.ExecuteNonQuery()
Catch ex As Exception
MsgBox("Backup DB failed" + ex.ToString)
Finally
conn.Close()
conn.Dispose()
End Try
A key thing to note above is the SqlConnection.ClearAllPools() statement. Even though I was sure that all connections had been properly closed and disposed of in other parts of my app - somehow the DBMS was still showing an open thread.
And now the Restore:
SqlConnection.ClearAllPools()
Dim connString As String = (server + ";Initial Catalog=master;Integrated Security=True;")
Dim conn As New SqlConnection(connString)
Dim sql As String
sql = "Use master;" _
& "Alter Database " & database & " Set Single_User With Rollback Immediate;" _
& "Restore Database " & database & " From Disk = #FILENAME" _
& " With Replace;" _
& "Alter Database " & database & " Set Multi_User;"
'execute restore
Dim dbcmd As New SqlCommand(sql, conn)
dbcmd.Parameters.AddWithValue("#FILENAME", cbdfilename)
conn.Open()
Try
dbcmd.ExecuteNonQuery()
Catch ex As Exception
MsgBox("Restore DB failed" + ex.ToString)
Finally
conn.Close()
conn.Dispose()
End Try
What was really weird in the SQL above is that I initially tried to use #Parms for the database name but the ALTER statements would not accept them. Kept kicking back with exceptions.
The biggest difference between my restore and the one from the earlier solution is that I only use the database name ie. "MyDB_TEST" and not the .mdf file name in my Alter and Restore statements.

Related

Set connection string for database access for production build Winforms app

I am working on a Winforms project, and I am just setting up the setup file for the installation of the program.
Scenario
The connection string I have set for my use is like this:
SqlConnection con = new SqlConnection(#"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=F:\Application\MyAppv1.0\MyApp\DB.mdf;Integrated Security=True");
return con;
Here, I use local DB from my personal location.
Question
On installation on the client's PC, the database is also attached, so the installed files will be on C:\Program Files\Author\MyAppv1.0. When I use the same connection string, the app shows an exception. So, should I update my connection string for that location during the setup creation? (I am using MSI).
Is there any commonly followed approach for this scenario?
My aim is to make the installed app use the DB that is also on the same location of installation named DB.mdf and is provided with setup.
Thanks guys in advance.
Do not use AttachDbFilename, it has many issues. Instead, attach your database normally using CREATE DATABASE...FOR ATTACH.
At startup of your app, you can connect to the server using master as the current DB, and check for existence of your DB. If it's not there you can create it.
private static void CheckDbExistence(string connectionString)
{
const string query = #"
IF NOT EXISTS (SELECT 1
FROM sys.databases
WHERE name = 'MyDb')
BEGIN
DECLARE #sql nvarchar(max) = N'
CREATE DATABASE MyDb ON
(FILENAME = ' + QUOTENAME(#mdf, '''') + '),
(FILENAME = ' + QUOTENAME(#ldf, '''') + ')
FOR ATTACH;
';
EXEC (#sql);
END;
";
var csb = new SqlConnectionStringBuilder(connectionString);
csb.Initial Catalog = "master";
using (var conn = new SqlConnection(csb.ConnectionString))
using (var comm = new SqlCommand(query, conn))
{
comm.Parameters.Add("#mdf", SqlDbType.NVarChar, 255).Value = Path.Combine(Environment.CurrentDirectory, "MyDb.mdf");
comm.Parameters.Add("#ldf", SqlDbType.NVarChar, 255).Value = Path.Combine(Environment.CurrentDirectory, "MyDb.ldf");
conn.Open();
comm.ExecuteNonQuery();
}
}

How to backup a database in WPF with C# and SQL Server? [duplicate]

This question already has answers here:
Backup Permissions
(5 answers)
Closed 1 year ago.
I'm trying to back up my database using this C# code How to backup and restore SQL Server in WPF with C# and Entity Framework
private static void CreateBackup(string databaseName, string backupFilePath)
{
GlobalConfig gb = new GlobalConfig();
string connectionString = gb.GetConnectionString();
backupFilePath = backupFilePath + "\\" + databaseName + ".bak";
backupFilePath = #""+backupFilePath;
var backupCommand = "BACKUP DATABASE #databaseName TO DISK = #backupFilePath";
using (var conn = new SqlConnection(connectionString))
using (var cmd = new SqlCommand(backupCommand, conn))
{
conn.Open();
cmd.Parameters.AddWithValue("#databaseName", databaseName);
cmd.Parameters.AddWithValue("#backupFilePath", backupFilePath);
cmd.ExecuteNonQuery();
}
}
CreateBackup("Test","C:\Desktop\Backup\\Test.bak");
But I got this error :
Cannot open backup device 'C:\Desktop\Backup\Test.bak'. Operating system error 5(Access is denied.).
What I'm doing wrong with this code?
How can I fix this error?
The SQL Server process typically does not run with the permissions of the currently logged in user, therefore it cannot access the users desktop (nor most of the folders of the user or any network folders). It is not possible to freely choose the folder for the backup.
Your best solution is to export to a folder where the server process has access to (i.e. the system temp folder) and then copy the backup from there to wherever you want it.
private static void CreateBackup(string databaseName, string backupFilePath)
{
GlobalConfig gb = new GlobalConfig();
string connectionString = gb.GetConnectionString();
// Create the backup in the temp directory (the server should have access there)
var backup = Path.Combine(Path.GetTempPath(), "TemporaryBackup.bak");
var backupCommand = "BACKUP DATABASE #databaseName TO DISK = #backup";
using (var conn = new SqlConnection(connectionString))
using (var cmd = new SqlCommand(backupCommand, conn))
{
conn.Open();
cmd.Parameters.AddWithValue("#databaseName", databaseName);
cmd.Parameters.AddWithValue("#backup", backup);
cmd.ExecuteNonQuery();
}
File.Copy(backup, backupFilePath); // Copy file to final location
}
I have successfully backed up SQL Server databases using Microsoft.SqlServer.Management.Smo.Backup, might want to try that. Mine was in VB years ago but it is still working today. Here is the VB code if it helps:
Dim mySourceServer As New Server(My.Settings.SQLInstance)
Dim bkpDBFullWithCompression As New Backup()
' Specify whether you want to back up database or files or log
Me.Cursor = Cursors.WaitCursor()
bkpDBFullWithCompression.Action = BackupActionType.Database
' Specify the name of the database to back up
bkpDBFullWithCompression.Database = _sBackupDatabaseName
bkpDBFullWithCompression.CompressionOption = BackupCompressionOptions.[On]
bkpDBFullWithCompression.Devices.AddDevice(_sBackupFilePath, DeviceType.File)
bkpDBFullWithCompression.BackupSetName = _sBackupDatabaseName + " database Backup - Compressed"
bkpDBFullWithCompression.BackupSetDescription = _sBackupDatabaseName + " database - Full Backup"
Try
bkpDBFullWithCompression.SqlBackup(mySourceServer)
Catch ex As SmoException
blSuccess = False '
Me.Cursor = Cursors.Default
End Try
This solution ( Check Local System account instead of This account ) worked for me, but I didn't have any idea if it's a good solution for security or no.
You can find the LogOn tab setting under this :
Services -> SQL Server -> Properties -> Log on

Impossible to DETACH database (from c#)

I can ATTACH database and copy all the data from one database to another.But on the end i can't detach it using same code.
var connection = new SQLiteConnection(connection)
connection.Open();
sqlAttachCommand = "ATTACH database '" + fileLoc + "' AS toMerge";
var cmd= new SQLiteCommand(sqlAttachCommand);
cmd.Connection = connection;
cmd.ExecuteNonQuery();
...
sqlAttachCommand = "DETACH database '" + fileLoc + "'";
The exception is:
SQL logic error or missing database no such database: C:\temp\database.db".
This is strange because I did attach for it and I did copy of all the data from this database.
The DETACH DATABASE command takes a schema-name as parameter, not a database file.
So your detach command should be like this:
sqlAttachCommand = "DETACH database toMerge";
as in your ATTACH command you named the thing toMerge.
In the SQL Exception the missing database isn't referring to the fact that SQLite lost that file, it is mere trying to tell that you're using a schema-name that doesn't exist.

LocalDB connections visible in SQL management studio

In my unit tests, I'm using a SQL Server LocalDB database. You could be nit picky and say that because of that fact it's not unit tests but integration tests and you would be right, but the point is that I am using the MSTest Framework to run those tests. Every test is copying an existing database and running their one test on this database.
private NAMETestSystem([CallerMemberName] string testCase = null)
{
this.destinationDirectory = Path.Combine(Directory.GetCurrentDirectory(), testCase ?? "Undefined_" + Guid.NewGuid().ToString("N"));
var connectionString = $"Data Source=(LocalDB)\\MSSQLLocalDB; Integrated Security = True; AttachDbFilename ={Path.Combine(this.destinationDirectory, "NAMEIntegrationTest.mdf")}";
var entityFrameworkData = $"metadata=res://*/NAME.csdl|res://*/NAME.ssdl|res://*/NAME.msl;provider=System.Data.SqlClient;provider connection string=\"{connectionString}\"";
// [...]
Copy(SourceDirectory, this.destinationDirectory);
My "problem" is that each of those copies pops up in my SQL Server management studio. All 100+ or them. I don't need them there. They don't exist anymore. And to make things worse, you cannot batch-detach... I have to press Del+Enter about 150 times just to clear that window up.
Is there a way to not have my temporary local db instances appear in my SQL server management studio?
Maybe special way to close or dispose, something in the connection string I can set? Or maybe a way to detach all of them at the same time in management studio?
So in the end, what I did and what is working fine for now is this:
public void Dispose()
{
// disposing other stuff
// sql db
if (Directory.Exists(this.destinationDirectory))
{
DetachDatabase(this.connectionString);
Directory.Delete(this.destinationDirectory, true);
}
}
public static void DetachDatabase(string connectionString)
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
var sql = "DECLARE #dbName NVARCHAR(260) = QUOTENAME(DB_NAME());\n" +
"EXEC('ALTER DATABASE ' + #dbName + ' SET OFFLINE WITH ROLLBACK IMMEDIATE;');\n" +
"EXEC('exec sp_detach_db ' + #dbName + ';');";
command.CommandText = sql;
command.ExecuteNonQuery();
}
}
}
Might not be the prettiest solution, but at least it keeps my sanity intact while the number of tests (and number of databases) rises.

Unable to restore SQL database, exclusive access could not be obtained (single user mode)

I am writing a simple database backup and restore routine for an application. I can backup my database without issues, however when I restore is I am unable to gain exclusive access to my database.
I am trying all the combinations of fixes on SO, putting in single user mode, taking it offline then placing it back only with no success.
I can successfully restore the database within studio manager (express)
This method is the only connection to the SQL server at the time, so I don't understand why I can't perform the restore.
Appreciate the help to point out where the issue may be.
internal void RestoreDatabase(string databaseFile)
{
//get database details
var databaseConfiguration = new DatabaseConfiguration().GetDatabaseConfiguration();
try
{
//construct server connection string
var connection = databaseConfiguration.IsSqlAuthentication
? new ServerConnection(databaseConfiguration.ServerInstance,
databaseConfiguration.SqlUsername,
databaseConfiguration.SqlPassword)
: new ServerConnection(databaseConfiguration.ServerInstance);
//set database to single user and kick everyone off
using (
var sqlconnection =
new SqlConnection(new DatabaseConfiguration().MakeConnectionString(databaseConfiguration)))
{
sqlconnection.Open();
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET Single_User WITH Rollback IMMEDIATE",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET OFFLINE",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET ONLINE",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
sqlconnection.Close();
}
//setup server connection and restore
var server = new Server(connection);
var restore = new Restore();
restore.Database = databaseConfiguration.DatabaseName;
restore.Action = RestoreActionType.Database;
restore.Devices.AddDevice(databaseFile, DeviceType.File);
restore.ReplaceDatabase = true;
restore.Complete += Restore_Complete;
restore.SqlRestore(server);
}
catch (Exception ex)
{
//my bad
restoreDatabaseServerError(ex.InnerException.Message, EventArgs.Empty);
}
finally
{
//set database to multi user
using (
var sqlconnection =
new SqlConnection(new DatabaseConfiguration().MakeConnectionString(databaseConfiguration)))
{
sqlconnection.Open();
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET Multi_User",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
sqlcommand.Dispose();
}
sqlconnection.Close();
}
}
}
If anybody is connected to your database, SQL Server cannot drop it, so you have to disconnect existing connections, as you have tried. The problem with single_user is, that it still allows a single user to connect. As you yourself cannot be connected to the database when dropping it you have to get out of there. That opens up that slot for someone else to connect and in turn prevent you from dropping it.
There are a few SQL Server processes that are particularly good at connecting to a database in that split second. Replication is one example. (You shouldn't really drop a database that is published anyway, bat that is another story.)
So what can we do about this? The only 100% safe way is to prevent users from connecting to the database. The only practical way is to switch the database offline and then drop it. However, that has the nasty side effect, that SQL Server does not delete the files of that database, so you have to do that manually.
Another option is to just be fast enough. In your example you bring the database back online before you drop it. That is a fairly resource intensive process that gives an "intruder" lots of time to connect.
The solution I have been using with success looks like this:
ALTER DATABASE MyDb SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE;
USE MyDb;
ALTER DATABASE MyDb SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
USE tempdb;
DROP DATABASE MyDb;
This first sets the database to restricted user and connects to it. Then, while still connected it sets the database to single user. Afterwards the context is switched to tempdb and the drop is executed immediately thereafter. Important here is, to send these commands as one batch to SQL Server to minimize the time between the USE tempdb; and the DROP. Setting the database to restricted user in the beginning catches some rare edge cases, so leave it in even though it does not make sense at first glance.
While this still leaves a theoretical gap for someone else to get in, I have never seen it fail.
After the database is dropped you can run your restore as normal.
Good luck.
Your restore needs to take place on the same connection you set the DB server to single user mode.
In summary for the changes below, I moved the end of the using to below your restore code, and moved the close for the SQL connection to after the restore so it uses the same connection. Also removed set offline and online since they aren't needed. Can't test at the moment, so let me know if it works.
//set database to single user and kick everyone off
using (var sqlconnection = new SqlConnection(new DatabaseConfiguration().MakeConnectionString(databaseConfiguration)))
{
sqlconnection.Open();
using (var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET Single_User WITH Rollback IMMEDIATE",sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
//setup server connection and restore
var server = new Server(sqlconnection);
var restore = new Restore();
restore.Database = databaseConfiguration.DatabaseName;
restore.Action = RestoreActionType.Database;
restore.Devices.AddDevice(databaseFile, DeviceType.File);
restore.ReplaceDatabase = true;
restore.Complete += Restore_Complete;
restore.SqlRestore(server);
sqlconnection.Close();
}

Categories

Resources