C# - Generate Database Backup Script In-Memory - c#

In a C# application, I know using ADO.NET we can execute a backup command like this:
BACKUP DATABASE MyDb TO DISK='C:\\MyDb.bak'
and take a database backup and store it at some given location.
I want to take backup of database in-memory i.e. return the backup script content (schema and data) which I can later save as .sql file at some location.
Is this possible?

I achieved it using SQL Server Management Objects (SMO). Thanks to all the friends who helped in comments.
First, install Microsoft.SqlServer.SqlManagementObjects from nuget package manager.
The working code:
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
var script = new StringBuilder();
Server server = new Server(new ServerConnection(new SqlConnection(connectionString)));
Database database = server.Databases[databaseName];
ScriptingOptions options = new ScriptingOptions
{
ScriptData = true,
ScriptSchema = true,
ScriptDrops = false,
Indexes = true,
IncludeHeaders = true
};
foreach (Table table in database.Tables)
{
foreach (var statement in table.EnumScript(options))
{
script.Append(statement);
script.Append(Environment.NewLine);
}
}
File.WriteAllText(backupPath + databaseName + ".sql", script.ToString());

Related

SQL Server database restore with CDC using SQL Management Objects in C#

I am restoring a full database backup from our production server to our staging server using the Restore class in SQL Management Objects, via a C# script task in SSIS. The database I am restoring is a CDC enabled database with a number of change tables - however these tables are being dropped once the restore has completed. I know that there is a KEEP_CDC option in the TSQL Restore command but I can't seem to find an equivalent property in SMO. Is there a way that I can do the restore using SMO whilst retaining the CDC capture instance? This is my code:
Server srv;
srv = new Server();
//kill all connections to the database prior to restore
srv.KillAllProcesses(databaseName);
Restore res = new Restore();
res.Database = databaseName;
res.Action = RestoreActionType.Database;
res.Devices.AddDevice(filePath, DeviceType.File);
res.ReplaceDatabase = true;
res.ReadFileList(srv);
DataTable filelist = res.ReadFileList(srv);
foreach (DataRow row in filelist.Rows)
{
string logicalFileName = Path.GetFileName(row["LogicalName"].ToString());
string physicalFileName = Path.GetFileName(row["PhysicalName"].ToString());
switch (Path.GetFileName(row["Type"].ToString()))
{
case "D":
res.RelocateFiles.Add(new RelocateFile(logicalFileName, Path.Combine(DataFilePath, physicalFileName)));
break;
case "L":
res.RelocateFiles.Add(new RelocateFile(logicalFileName, Path.Combine(LogFilePath, physicalFileName)));
break;
}
}
res.SqlRestore(srv);

Sql server SMO partial backup

I have a database in SQL Server 2008 R2, that uses the Simple recovery model.
The database contains a filegroup, where the bulk of the data resides (>20GB of images). These images are not critical for the application.
I want to backup the database from C# using Sql Server SMO. But I only want to backup the database structure (the PRIMARY filegroup; everything except the non-essential images). I want to do this in order to keep the backup size small.
In my C# code, I am setting the backup action to BackupActionType.Files, and I am only including the PRIMARY filegroup inside the DatabaseFileGroups collection, so it should only backup the database structure, and not the images.
But when I run the backup, I get this exception:
System.Data.SqlClient.SqlError: The primary filegroup cannot be backed up as a file backup because the database is using the SIMPLE recovery model. Consider taking a partial backup by specifying READ_WRITE_FILEGROUPS.
My question is, how can I specify READ_WRITE_FILEGROUPS from inside C# code, using Sql Server SMO? The exception shows me how to do so in T-SQL, but I want to do the same thing in C#.
Here is the code I am using:
class Program
{
static string DbName = PATH_TO_DATABASE;
static string connString = CONNECTION_STRING;
static void Main(string[] args)
{
ServerConnection serverConn = new ServerConnection();
serverConn.ConnectionString = connString;
Server server = new Server(serverConn);
Backup backup = new Backup() { Database = DbName };
backup.Action = BackupActionType.Files;
backup.DatabaseFileGroups.Add("PRIMARY");
backup.Devices.AddDevice("D:\\backup.bak", DeviceType.File);
backup.Initialize = true;
backup.ContinueAfterError = false;
backup.Incremental = false;
backup.Complete += (snd, e) => { Console.WriteLine("Complete"); };
backup.PercentComplete += (snd, e) => { Console.WriteLine("Percent " + e.Percent); };
backup.SqlBackup(server);
serverConn.Disconnect();
}
}
solution is very simple.
Just in SQLSERVER rigth-click on database and in Properties Window in Option tab change Recovery Mode To Bulk-logged
secound Solution by T-SQL:
USE [master]
GO
ALTER DATABASE [databasename] SET RECOVERY BULK_LOGGED WITH NO_WAIT
GO

Visual studio mdf database structure modified then lost after publish

Service-based database is new to me. I would like to create a simple database application with:
Service-based database -> Dataset (mdf)
LINQ to SQL (L2S) Classes
This application will be installed on a lot of individual machines every instances has it's own mdf database.
Installation is done by Clickonce.
My problem is:
I publish my application and install it on user machines
Users put some data into the database
Turns out that we need another table or column
Publish the application again with the extended database and install on user machines
User starts with a new database and original data lost!
(If I am not modifying the database structure than all data is in the database after next Clickone update)
Questions:
If I made only alter table- or add table- like modifications is there any way to preserve data during the next Clickonce update?
Thank you in advance!
Dave
I have found a solution.
If you modify your database model in visual studio Clickonce will automatically recreate all tables after installation (newly published application with modified tables).
Clickonce save the old database to a specified location:
if (ApplicationDeployment.IsNetworkDeployed && ApplicationDeployment.CurrentDeployment.DataDirectory != null)
string preDatabase = Path.GetFullPath(Path.Combine(ApplicationDeployment.CurrentDeployment.DataDirectory,#".pre\sampledatabase.mdf"));
This .pre directory is created by Clickonce. You always check if this file is exist or not. If it is exist you have to copy data from old tables to new tables OR you will loose all data from old tables!
How to copy data from one database to another very similar database? My answer is the following: copy all table to another table with SqlBulkCopy.
// Create source connection
using (
var source =
new SqlConnection(
String.Format(
#"Data Source=(LocalDB)\v11.0;AttachDbFilename={0};Integrated Security=True;",
preDatabase)))
{
source.Open();
// Create destination connection
using (var destination = new SqlConnection(Settings.Default.mdcdbConnectionString))
{
destination.Open();
DataTable dt = source.GetSchema("Tables");
foreach (string tablename in from DataRow row in dt.Rows select (string) row[2])
{
App.Logger.LogText(String.Format("Copying table {0}", tablename));
using (var cmd = new SqlCommand(String.Format("TRUNCATE TABLE {0}", tablename), destination))
{
cmd.ExecuteNonQuery();
//App.Logger.LogText(String.Format("truncate table {0}", tablename));
}
using (var cmd = new SqlCommand(String.Format("SELECT * FROM {0}", tablename), source))
{
using (SqlDataReader reader = cmd.ExecuteReader())
{
var bulkData = new SqlBulkCopy(destination)
{
DestinationTableName = tablename
};
// Set destination table name
bulkData.WriteToServer(reader);
// Close objects
bulkData.Close();
//App.Logger.LogText(String.Format("Copy success {0}", tablename));
}
}
}
destination.Close();
}
source.Close();
}
Wish you good coding!
Dave

Copying table from one SQL Server to another

I'm trying to create a copy of a table (no data, just the schema) using SQL Server Management Objects (SMO), Transfer class. The only thing I haven't figured out is how to specify what server to copy to, when the servers are on different hosts. In my case, I want to copy from 10.1.2.x to 10.1.2.y. Is there any way to specify this, or does this class not support it?
Perhaps there are better C# solutions?
static void CreateTableFromTable(string fromConnection, string toConnection, string dbName, string tablename, bool copyData = false)
{
Server fromServer = new Server(new ServerConnection(new SqlConnection(fromConnection)));
Database db = fromServer.Databases[dbName];
Transfer transfer = new Transfer(db);
transfer.CopyAllObjects = false;
transfer.DropDestinationObjectsFirst = false;
transfer.CopySchema = false; //Database schema? Or Table schema? I DO NOT want to overwrite the db schema
transfer.CopyData = copyData;
transfer.DestinationServer = "?";
transfer.DestinationDatabase = dbName;
transfer.Options.IncludeIfNotExists = true;
transfer.ObjectList.Add(db.Tables[tablename]);
transfer.TransferData();
}
Have you tried import and export data wizard even importing with table data ,command line or GUI in SQL server 2005/8 and Mysql /MysqlWorkbench.
I'm not sure if you found another solution - or got this one working. If you did not the SMO Scripter object might be worth a look.
This MSDN example could be helpful. You could script the tables and dependencies you want and then open a connection to the destination database and execute the scripts.
static void Main(string[] args)
{
Server sourceServer = new Server("server");
String dbName = "database";
// Connect to the local, default instance of SQL Server.
// Reference the database.
Database db = sourceServer.Databases[dbName];
// Define a Scripter object and set the required scripting options.
Scripter scripter = new Scripter(sourceServer);
scripter.Options.ScriptDrops = false;
scripter.Options.WithDependencies = true;
scripter.Options.Indexes = true; // To include indexes
scripter.Options.DriAllConstraints = true; // to include referential constraints in the script
// Iterate through the tables in database and script each one. Display the script.
foreach (Table tb in db.Tables)
{
// check if the table is not a system table
if (tb.IsSystemObject == false)
{
Console.WriteLine("-- Scripting for table " + tb.Name);
// Generating script for table tb
System.Collections.Specialized.StringCollection sc = scripter.Script(new Urn[] { tb.Urn });
foreach (string st in sc)
{
//ado.net to destination
Console.WriteLine(st);//SqlCommand.ExecuteNonQuery();
}
Console.WriteLine("--");
}
}
}
Did you try to use SELECT ... INTO statement?
For example:
SELECT * INTO DestDatabase.TableName FROM SourceDatabase.TableName
If you don't want to copy data, just add a condition which will be return nothing, ex: WHERE Id = 0

System.Data.sqlClient will only create default database on server

Background:
I am using sql statements to create a Temp database on a server which will store data until it is needed further by my client program.
Problem:
My sql statement to create the database works properly and creates the database with all the required specifications when run through Sql Management studio, on the other hand when my program executes the statement it only creates a database with the 'Default' settings except for the name.
Questions:
Why is this?
How can I make it create a database with my specifications
Sql statement:
CREATE DATABASE Temp ON PRIMARY(
NAME = Temp
, FILENAME = 'C:\Temp.mdf'
, SIZE = 2MB
, FILEGROWTH = 10%) LOG ON (
NAME = Temp_Log
, FILENAME = 'C:\Temp.ldf'
, SIZE = 1MB, MAXSIZE = 70MB
, FILEGROWTH = 10%)
Code:
public void AcuConvert()
{
using (DestD)
{
SqlCommand command = new SqlCommand();
DestD.Open();
command.Connection = DestD;
foreach (var item in Entity.SqlDestinationQueries.ToList())
{
command.CommandText = item.Query;
command.ExecuteNonQuery(); //This is where the command is run
}
foreach (var item in Entity.SystemQueries.ToList())
{
command.CommandText = item.Query.Replace("#Sys", SysD.Database);
command.ExecuteNonQuery();
}
foreach (var item in Entity.InsertQueries.ToList())
{
command.CommandText = item.Query.Replace("#Source", SourceD.Database); ;
command.ExecuteNonQuery();
}
}
}
Have you tried using SQL Server Management Objects instead of a raw SQL statement?
For example:
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
...
// Connect to the default instance
Server server = new Server();
// Establish new database details
Database database = new Database(server, "MyTempDB");
// Add primary filegroup details
database.FileGroups.Add(new FileGroup(database, "PRIMARY"));
// Set Primary datafile properties
DataFile primaryFile = new DataFile(database.FileGroups["PRIMARY"],
"MyTempDB_Data", "C:\\MyTempDB.mdf");
primaryFile.Size = 2048; // Sizes are in KB
primaryFile.GrowthType = FileGrowthType.Percent;
primaryFile.Growth = 10;
// Add to the Primary filegroup
database.FileGroups["PRIMARY"].Files.Add(primaryFile);
// Define the log file
LogFile logfile = new LogFile(database, "MyTempDB_Log", "C:\\MyTempDB_Log.ldf");
logfile.Size = 1024;
logfile.GrowthType = FileGrowthType.Percent;
logfile.Growth = 10;
logfile.MaxSize = 70 * 1024;
// Add to the database
database.LogFiles.Add(logfile);
// Create
database.Create();
database.Refresh();
You can connect to the server with specific credentials, too, of course:
http://msdn.microsoft.com/en-us/library/microsoft.sqlserver.management.smo.server.aspx
If, however, you're stuck with using text scripts to create your database, I'd ensure that your Initial Catalog is set correctly (i.e. to 'master') in your connection string and your user has the necessary permissions - CREATE DATABASE / CREATE ANY DATABASE / ALTER DATABASE. If this still doesn't give you any joy, try stripping out the rest of your C# code and run the create SQL independently of the other statements - it could be that there's a side-effect from a preceding query. Use Profiler to see exactly what's running as you add them back in.
Edit:
I tested your script against a local SQL Server instance (2012 SqlLocalDB) via a small C# program using SqlClient and it ran just fine after changing the file paths to ones I had write access to (root of C is protected by default). The only other amendment was that the Primary size had to start at 3MB or more. Any smaller and the default tables could not be created. This may be another avenue of investigation for you to explore.
Your alternative option could be to use the Process class to run the sqlcmd.exe
Eg.
var process = Process.Start(WORKING_PATH, argument);
%WORKING_PATH% being "C:\tools\sql\sqlcmd.exe"
%argument% being "C:\scripts\Create.sql"
I use this strategy to dump test data into test environments when bootstrapping acceptance test fixtures.
Cheers

Categories