A simple database backup restore class based on ADO.net
During development/testing, to backup/restore database is a common task. Manual clicks are not ideal solution, to automation, commonly we have two solutions:
- Leverage database server feature, such as SQLServer backup/restore, which is database level.
- Backup/Restore in table level, store it in file.
Neither of them is perfect:
Solution1: DB Level | Solution 2: Table Level | |
PROS | The feature is provided by product which should be stable.Foreign key can be handled correctly and easily |
Fine grained control, be able to skip some tables to get faster speed.Easy to deal with Replication/Mirroring/Logshipping |
CONS | Very difficult under multi-server environment. Such as Replication, Mirroring, etc. | If table has foreign key, the logic would be very complicated |
I am lucky that our new system adopts the foreign key free sql design, so solution 2 looks a good choice for us.
Here is a very simple class to do the back/restore job:
To enumerate table names:
1: public List<string> GetTableNames()
2: {
3: List<string> result = new List<string>();
4: string tableName = string.Empty;
5: string sql = "SELECT name AS table_name, * FROM sys.tables WHERE Type = 'U'";
6: using (SqlConnection conn = new SqlConnection(ConnectionString))
7: using (DataTable table = new DataTable())
8: using (SqlDataAdapter adapter = new SqlDataAdapter(sql, conn))
9: {
10: adapter.Fill(table);
11: foreach (DataRow row in table.Rows)
12: {
13: result.Add(Convert.ToString(row["table_name"]));
14: }
15: }
16: result.Sort();
17: return result;
18: }
To backup tables to DataSet/String:
1: /// <summary>
2: /// Pass null as includedTables if you want to backup all.
3: /// Actual tables = includedTables - excludedTables
4: /// </summary>
5: public DataSet BackupDatabase(List<string> includedTables = null, List<string> excludedTables = null)
6: {
7: includedTables = includedTables ?? GetTableNames();
8: if (excludedTables != null)
9: {
10: includedTables = includedTables.Except(excludedTables, StringComparer.OrdinalIgnoreCase).ToList();
11: }
12:
13: DataSet result = new DataSet();
14: using (SqlConnection conn = new SqlConnection(ConnectionString))
15: {
16: includedTables.ForEach(t =>
17: {
18: string sql = "select * from " + t;
19: using (SqlDataAdapter adapter = new SqlDataAdapter(sql, conn))
20: {
21: adapter.Fill(result, t);
22: }
23: });
24: }
25: return result;
26: }
27:
28: public string BackupDatabaseToString(List<string> includedTables = null, List<string> excludedTables = null)
29: {
30: using (var dataSet = BackupDatabase(includedTables, excludedTables))
31: using (MemoryStream ms = new MemoryStream())
32: {
33: dataSet.WriteXml(ms, XmlWriteMode.WriteSchema);
34: return Encoding.UTF8.GetString(ms.ToArray());
35: }
36: }
To restore table from DataSet:
1: public void RestoreDatabase(DataSet newDataSet)
2: {
3: using (TransactionScope scope = new TransactionScope())
4: using (SqlConnection conn = new SqlConnection(ConnectionString))
5: using (DataSet currentDataSet = new DataSet())
6: {
7: conn.Open();
8: foreach (DataTable newTable in newDataSet.Tables)
9: {
10: string tableName = newTable.TableName;
11: SqlCommand deleteAllCmd = new SqlCommand("delete from " + tableName, conn);
12: deleteAllCmd.ExecuteNonQuery();
13: // reseed the indetity columns
14: SqlCommand reseedCmd = new SqlCommand(String.Format("DBCC CHECKIDENT ('{0}', RESEED, -1)", tableName), conn);
15: try
16: {
17: reseedCmd.ExecuteNonQuery();
18: }
19: catch { } // ignore the error from table without identity columns
20:
21: using (SqlDataAdapter adapter = new SqlDataAdapter("select * from " + tableName, conn))
22: {
23: adapter.Fill(currentDataSet, tableName);
24: foreach (DataRow newRow in newTable.Rows)
25: {
26: currentDataSet.Tables[tableName].Rows.Add(newRow.ItemArray);
27: }
28: Console.WriteLine("restoring table '{0}' with {1} rows", tableName, newTable.Rows.Count);
29: SqlCommandBuilder dummyCmd = new SqlCommandBuilder(adapter); // this would generate insertCommand inside adapter
30: adapter.Update(currentDataSet, tableName);
31: }
32: }
33: scope.Complete();
34: }
35: }
You can use this in your c# automated test case, or powershell.