Using SMO to copy a database and data - c#

I am trying to make a copy of a database to a new database on the same server. The server is my local computer running SQL 2008 Express under Windows XP.
Doing this should be quite easy using the SMO.Transfer class and it almost works!
My code is as follows (somewhat simplified):
Server server = new Server("server");
Database sourceDatabase = server.Databases["source database"];
Database newDatbase = new Database(server, "new name");
newDatbase.Create();
Transfer transfer = new Transfer(sourceDatabase);
transfer.CopyAllObjects = true;
transfer.Options.WithDependencies = true;
transfer.DestinationDatabase = newDatbase.Name;
transfer.CopySchema = true;
transfer.CopyData = true;
StringCollection transferScript = transfer.ScriptTransfer();
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand switchDatabase = new SqlCommand("USE " + newDatbase.Name, conn))
{
switchDatabase.ExecuteNonQuery();
}
foreach (string scriptLine in transferScript)
{
using (SqlCommand scriptCmd = new SqlCommand(scriptLine, conn, transaction))
{
int res = scriptCmd.ExecuteNonQuery();
}
}
}
What I do here is to first create a new database, then generate a copy script using the Transfer class and finally running the script in the new database.
This works fine for copying the structure, but the CopyData option doesn't work!
Are there any undocumented limits to the CopyData option? The documentation only says that the option specifies whether data is copied.
I tried using the TransferData() method to copy the databse without using a script but then I get an exception that says "Failed to connect to server" with an inner exception that says "A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: Named Pipes Provider, error: 40 - Could not open a connection to SQL Server)"
I also tried to enable Named Pipes on the server, but that doesn't help.
Edit:
I found a solution that works by making a backup and then restoring it to a new database. It's quite clumsy though, and slower than it should be, so I'm still looking for a better solution.

Well, after contacting Microsft Support I got it working properly, but it is slow and more or less useless. Doing a backup and then a restore is much faster and I will be using it as long as the new copy should live on the same server as the original.
The working code is as follows:
ServerConnection conn = new ServerConnection("rune\\sql2008");
Server server = new Server(conn);
Database newdb = new Database(server, "new database");
newdb.Create();
Transfer transfer = new Transfer(server.Databases["source database"]);
transfer.CopyAllObjects = true;
transfer.CopyAllUsers = true;
transfer.Options.WithDependencies = true;
transfer.DestinationDatabase = newdb.Name;
transfer.DestinationServer = server.Name;
transfer.DestinationLoginSecure = true;
transfer.CopySchema = true;
transfer.CopyData = true;
transfer.Options.ContinueScriptingOnError = true;
transfer.TransferData();
The trick was to set the DestinationDatabase property. This must be set even if the target is that same as the source. In addition I had to connect to the server as a named instance instead of using the other connection options.

Try setting SetDefaultInitFields to true on the Server object.
I had the same issue with the SMO database object running slowly. I guess this is because sql server doesn't like to retrieve entire objects and collections at once, instead lazy loading everything, causing a round-trip for each field, which for an entire database is pretty inefficient.

I had a go at getting this working and have come up with an answer that doesn't use the Transfer class. Here is the Method i used:
public bool CreateScript(string oldDatabase, string newDatabase)
{
SqlConnection conn = new SqlConnection("Data Source=.;Initial Catalog=" + newDatabase + ";User Id=sa;Password=sa;");
try
{
Server sv = new Server();
Database db = sv.Databases[oldDatabase];
Database newDatbase = new Database(sv, newDatabase);
newDatbase.Create();
ScriptingOptions options = new ScriptingOptions();
StringBuilder sb = new StringBuilder();
options.ScriptData = true;
options.ScriptDrops = false;
options.ScriptSchema = true;
options.EnforceScriptingOptions = true;
options.Indexes = true;
options.IncludeHeaders = true;
options.WithDependencies = true;
TableCollection tables = db.Tables;
conn.Open();
foreach (Table mytable in tables)
{
foreach (string line in db.Tables[mytable.Name].EnumScript(options))
{
sb.Append(line + "\r\n");
}
}
string[] splitter = new string[] { "\r\nGO\r\n" };
string[] commandTexts = sb.ToString().Split(splitter, StringSplitOptions.RemoveEmptyEntries);
foreach (string command in commandTexts)
{
SqlCommand comm = new SqlCommand(command, conn);
comm.ExecuteNonQuery();
}
return true;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("PROGRAM FAILED: " + e.Message);
return false;
}
finally
{
conn.Close();
}
}

Here is my solution:
I have a Database named is Olddatabase
I backup it to E:\databackup\Old.bak
If you want to create a Duplicate Database from Olddatabase in the same server with name NewDatabase
3.1 You can use command in query tool : EXEC OldDatabase.dbo.sp_helpfile;
to determinat path of OldDatabase is stored in case you want to save NewDatabase in the same folder.
or You can save NewDatabase in new Path which you want
use this command in Query tool
RESTORE DATABASE NewDatabase FROM DISK = 'E:\databackup\Old.bak'
WITH MOVE 'OldDatabase' TO 'E:\New path (or the same path)\NewDatabase_Data.mdf',
MOVE 'OldDatabase_log' TO 'E:\New path (or the same path)\NewDatabase_Log.ldf';
Note: you can Use these command obove in c# with : Create a Store procedure in sql which include Above commands. And you can call the store procedure in C #

Related

Programmatically modifying the file path location for a C# SMO created database?

I am trying to programmatically create a new database using SMO in C#. For this project, I do not want the .mdf/.ldf files placed in the default folder
"C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL2008R2\MSSQL\DATA". I have not found anything on the web that tells how to modify the setting for the file location.
I get a failed operation exception when I run the following code:
Server srv = new Server(serverName.Text);
var db = new Database(srv, dbName.Text);
db.Create();
DataFile df = new DataFile(db.FileGroups["PRIMARY"],
dbName.Text, pathText.Text + dbName.Text + "_data.mdf");
df.Create();
LogFile lf = new LogFile(db, "Log01", pathText.Text + dbName.Text + "_log.ldf");
lf.Create();
The exception occurs at the df.Create(); line.
Any ideas?
I think this one answers the question for you.
Use SMO to Change SQL Server Database Default Locations
TTRider's Answer was:
You need to add information about Data and Log files explicitly:
TTRider's answer in the linked question points in the right direction, but is incomplete. Using some additional info from this post I was able to get it working (tested with SMO 2016 libraries).
private void CreateDatabase(string connectionString, string databaseName, string dataFilePath)
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
ServerConnection serverConnection = new ServerConnection(sqlConnection);
Server sqlServer = new Server(serverConnection);
Database smoDatabase = new Database(sqlServer, databaseName);
string dataFileName = string.Format("{0}_Data", databaseName);
string dataFileFullPath = Path.ChangeExtension(Path.Combine(dataFilePath, dataFileName), ".mdf");
string logFileName = string.Format("{0}_Log", databaseName);
string logFileFullPath = Path.ChangeExtension(Path.Combine(dataFilePath, logFileName), ".ldf");
FileGroup fileGroup = new FileGroup(smoDatabase, "PRIMARY");
smoDatabase.FileGroups.Add(fileGroup);
DataFile dataFile = new DataFile(fileGroup, dataFileName, dataFileFullPath);
dataFile.IsPrimaryFile = true;
fileGroup.Files.Add(dataFile);
LogFile logFile = new LogFile(smoDatabase, logFileName, logFileFullPath);
smoDatabase.LogFiles.Add(logFile);
smoDatabase.Create();
serverConnection.Disconnect();
}
}

Connecting to mysql on 000webhost using C#

Im simply just trying to read what there is in the batabase on to a console but i always get an exception on the conn.Open() line. Here is all the code:
SqlConnectionStringBuilder conn_string = new SqlConnectionStringBuilder();
conn_string.DataSource = "mysql14.000webhost.com"; // Server
conn_string.UserID = "a7709578_codecal";
conn_string.Password = "xxxxx";
conn_string.InitialCatalog = "a7709578_codecal"; // Database name
SqlConnection conn = new SqlConnection(conn_string.ToString());
conn.Open();
SqlCommand cmd = new SqlCommand("Select name FROM Users");
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
Console.WriteLine("{1}, {0}", reader.GetString(0), reader.GetString(1));
}
reader.Close();
conn.Close();
if (Debugger.IsAttached)
{
Console.ReadLine();
}
You need to build the connection string manually or use MySqlConnectionStringBuilder. MySql uses a different format than SQL Server and the SqlConnectionStringBuilder that you're using. You also need to use a MySQL library, SqlConnection, SqlCommand, etc are all build specifically for SQL Server.
MySQL connectors
For MySQL database you are using wrong provider. Those classes you have used in posted code are for SQL Server. Your code should look like below with MySQL provider related classes
MySqlConnectionStringBuilder conn_string = new MySqlConnectionStringBuilder();
conn_string.Server = "mysql14.000webhost.com";
conn_string.UserID = "a7709578_codecal";
conn_string.Password = "xxxxxxx";
conn_string.Database = "a7709578_codecal";
using (MySqlConnection conn = new MySqlConnection(conn_string.ToString()))
Check Related post in SO
Also to point out, you are selecting only one column from your table as can be seen
new SqlCommand("Select name FROM Users");
Whereas trying to retrieve two column value, which is not correct
Console.WriteLine("{1}, {0}", reader.GetString(0), reader.GetString(1))
000webhost free servers does not allow external connections to the server database.
You can only use your database from your PHP scripts stored on the server.
You can get data from database using PHP and it will return.So i advice to you using php from C# like api.

Restore database using c# and smo

I am trying to restore a sql server .bak into an empty database using the following c# code:
string dbBakFile = GetBackFileFromZip(restoreConfig.TmpUnZipFolder,restoreConfig.DatabaseFileToRestore);
if (string.IsNullOrEmpty(dbBakFile))
{
response.Status = DatabaseResponseStatus.Error;
response.Message = "No .bak file found in " + restoreConfig.DatabaseToRestore;
return response;
}
var builder =
new SqlConnectionStringBuilder(
ConfigurationManager.ConnectionStrings["myserver"].ConnectionString);
var smoServer =
new Server(new ServerConnection(builder.DataSource,builder.UserID,builder.Password));
var db = smoServer.Databases[restoreConfig.DatabaseToRestore];
if (db != null)
{
smoServer.KillAllProcesses(restoreConfig.DatabaseToRestore);
log.Debug("all processes on db killed");
}
string dbPath = Path.Combine(db.PrimaryFilePath, restoreConfig.DatabaseToRestore + ".mdf");
log.Debug("db path is " +dbPath);
string logPath = Path.Combine(db.PrimaryFilePath,restoreConfig.DatabaseToRestore + "_Log.ldf");
log.Debug("log path is " + logPath);
var restore = new Restore();
var deviceItem =
new BackupDeviceItem(dbBakFile, DeviceType.File);
restore.DatabaseFiles.Add(dbPath);
restore.DatabaseFiles.Add(logPath);
restore.Devices.Add(deviceItem);
restore.Database = restoreConfig.DatabaseToRestore;
restore.FileNumber = 1;
restore.Action = RestoreActionType.Files;
restore.ReplaceDatabase = true;
restore.PercentCompleteNotification = 10;
restore.PercentComplete +=restore_PercentComplete;
restore.Complete += restore_Complete;
restore.SqlRestore(smoServer);
db = smoServer.Databases[restoreConfig.DatabaseToRestore];
db.SetOnline();
smoServer.Refresh();
db.Refresh();
I get the following error:
Microsoft.SqlServer.Management.Smo.FailedOperationException: Restore failed for Server 'IM-M4500\SQLEXPRESS'. ---> Microsoft.SqlServer.Management.Smo.SmoException: System.Data.SqlClient.SqlError: The backup set holds a backup of a database other than the existing 'new-test-44444' database
Yes it does hold different backup and I want to overwrite and replace it also want to move mdf and log files to new files. Am I missing something here in the options of restore?
Many thanks
Ismail
Ok fixed the issue I need to give it the current db logical file name what i was actually doing was giving it the new db logical file name so
//get the logical file names
DataTable dtFileList = restore.ReadFileList(smoServer);
string dbLogicalName = dtFileList.Rows[0][0].ToString();
string logLogicalName = dtFileList.Rows[1][0].ToString();
restore.RelocateFiles.Add(GetRelocateFile(dbLogicalName, dbPath));
restore.RelocateFiles.Add(GetRelocateFile(logLogicalName, logPath));
This works nicely.
Don't create a new database and try to restore on it. Instead use the below query.
RESTORE DATABASE dbname from disk='location' WITH MOVE 'data' TO 'name.mdf' MOVE '_Log' TO 'name_log.ldf'
To Replace the existing database, put its name on the dbname and use WITH REPLACE on the query

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

How to save data in the different database depending on the internet connection

I want to make my code to be able to check if internet connection is established.
After that I will normally save records in the database on the server, but I want to be able to save records in the local database on pc everytime the connection is lost and before every normal connection on the server check if the local database is empty and copy everything from local database to server database.
Here is my code that I use now:
//open database connection
con = new MySqlConnection("server=192...;database=GPS_data;uid=root;pwd=******");
con.Open();
//check if card reader is loged
if (card_number != null)
{
cmd = new MySqlCommand("insert into data values (null, ?Parname , ?Parname2, ?Parname3, ?Parname4, ?Parname5, ?Parname6, ?Parname7);", con);
cmd.Parameters.Add("?Parname", MySqlDbType.Double).Value = Math.Round(deciLat, 5);
cmd.Parameters.Add("?Parname2", MySqlDbType.Double).Value = Math.Round(deciLon, 5);
cmd.Parameters.Add("?Parname3", MySqlDbType.Timestamp).Value = DateTime.Now;
cmd.Parameters.Add("?Parname4", MySqlDbType.VarChar).Value = card_number;
cmd.Parameters.Add("?Parname5", MySqlDbType.VarChar).Value = ConfigSettings.ReadSetting("reg");
cmd.Parameters.Add("?Parname6", MySqlDbType.VarChar).Value = ConfigSettings.ReadSetting("ser");
cmd.Parameters.Add("?Parname7", MySqlDbType.Double).Value = ellipHeight;
cmd.ExecuteNonQuery();
lastDBUpdate = DateTime.Now;
}
else //in the case when user is not logged in with the card
{
cmd = new MySqlCommand("insert into data values (null, ?Parname , ?Parname2, ?Parname3, ?Parname4, ?Parname5, ?Parname6, ?Parname7);", con);
cmd.Parameters.Add("?Parname", MySqlDbType.Double).Value = Math.Round(deciLat, 5);
cmd.Parameters.Add("?Parname2", MySqlDbType.Double).Value = Math.Round(deciLon, 5);
cmd.Parameters.Add("?Parname3", MySqlDbType.Timestamp).Value = DateTime.Now;
cmd.Parameters.Add("?Parname4", MySqlDbType.VarChar).Value = null;
cmd.Parameters.Add("?Parname5", MySqlDbType.VarChar).Value = ConfigSettings.ReadSetting("reg");
cmd.Parameters.Add("?Parname6", MySqlDbType.VarChar).Value = ConfigSettings.ReadSetting("reg");
cmd.Parameters.Add("?Parname7", MySqlDbType.Double).Value = ellipHeight;
cmd.ExecuteNonQuery();
lastDBUpdate = DateTime.Now;
}
So this part of the code goes on the server.
I mean there shouldn't be any special connection check as this would probably result with an error if connection is not established.
I want to add saving to a local database depending on connection, so connection=lost ( save in the local databse), connection=established(first check if local database is empty= if not copy to server database, continue recording on server)
Create a function GetConnectionString() and use this function to get connection always.
Write down the code to check for internet existence and return the connection on basis of that.
public string GetConnectionString()
{
string SqlConString1 = value;//Read from config
string SqlConString2 = value;//Read from config
WebClient client = new WebClient();
try
{
using (client.OpenRead("http://www.google.com"))
{
}
return SqlConString1 ;
}
catch (WebException)
{
return SqlConString2 ;
}
}
Refer this for more code to check for internet connectivity 1
Refer this for more code to check for internet connectivity 2
Refer this for more code to check for internet connectivity 3
Create function that returns connection object depends of any logic. Then make transaction to this object and always check result. If result is not ok, then depends on connection type make dession what to do with data.

Categories