SQL Server Express 自动备份控制台应用程序 C# ADO.NET
SQL Server Express自动备份控制台应用程序
引言
这是一个简单的 .NET V 2.0 控制台应用程序,旨在与 Windows 任务计划程序结合使用,以启用 SQL Server Express 数据库的自动备份(因为 SQL Server Express 没有 SQL Server Agent 或任何其他创建备份计划的方法)。 当然,它也可以与 SQL Server 完整版一起使用。 它已经在 SQL Server 2005 Express 和 SQL Server 2008 上进行了测试。

该应用程序使用 TSQL 脚本遍历连接字符串指向的位置的数据库引擎下的非系统数据库,并在配置的位置创建备份。 应用程序可以选择将日期添加到文件名中。
如果在文件名中包含日期,则可以将应用程序配置为删除早于特定天数的数据库。
这是一个方便的小应用程序,已经在办公室中使用了一段时间,我们决定将其贡献给 Code Project,以帮助任何尝试解决类似问题的人(我们不是想赢得任何美学竞赛!)。
因为此程序可能会删除名称中包含日期字符串的数据库,所以请确保在将此应用程序安装在包含任何重要数据库的系统上之前,了解代码的工作原理 - 使用风险自负。
TSQL 的功劳归于 http://www.mssqltips.com/tip.asp?tip=1070。
Using the Code
除了日志记录代码之外,所有代码都在 Program
类中。
安装完成后,使用 Windows 任务计划程序按需运行该应用程序。
该应用程序使用 App.config 文件(或编译后的 SQLScheduleBackup.exe.cofig )进行配置。 您只需要提供连接字符串,并告诉应用程序要将备份保存在哪里。
使用 DeleteOldBackups
和 DeleteBackupsAfterDays
设置备份在多少天后被删除。 备份需要使用 DateStampBackupFiles
设置为 true
创建,因为使用文件名而不是文件属性来确定文件的年龄。
<?xml version="1.0"?>
<configuration>
<appSettings>
<!-- SQL Server connection string -->
<add key="DBConnectionString"
value="Data Source=.\SQLEXPRESS;Initial Catalog=master;Integrated Security=true"/>
<!-- Path must have trailing slash -->
<add key="SQLBackupLocation" value="C:\SQL_Server_2005_Express_Backup\"/>
<!-- Path must have trailing slash -->
<add key="LoggingPath" value="C:\SQL_Server_2005_Express_Backup\Logs\"/>
<!-- Boolean-->
<add key="DateStampBackupFiles" value="True"/>
<!-- Boolean-->
<add key="DeleteOldBackups" value="True"/>
<!-- Integer -->
<add key="DeleteBackupsAfterDays" value="28"/>
<!-- Integer -->
<add key="ConsoleWaitBeforeCloseSeconds" value="60"/>
</appSettings>
<startup><supportedRuntime version="v2.0.50727"/></startup></configuration>
Main()
方法仅控制程序流程。
static void Main( string[] args )
{
Initialise();
Console.WriteLine
( "Cognize Limited Copyright 2009 - http://www.cognize.co.uk\n" );
Console.WriteLine( "SQL Server Backup is starting...\n" );
Output( "Starting SQL Database Backup...\n" );
bool doDateStamp =
bool.Parse( ConfigurationManager.AppSettings
["DateStampBackupFiles"].ToString() );
bool success = DoBackups( m_backupDir, doDateStamp );
if (success)
{
Output( "Backup of SQL Server Databases ran with no errors.\n\n" );
if (Boolean.Parse
( ConfigurationManager.AppSettings["DeleteOldBackups"] ) == true)
{
DeleteOldBackups();
}
}
int counter = int.Parse( ConfigurationManager.AppSettings
["ConsoleWaitBeforeCloseSeconds"] );
Console.WriteLine( "" );
while ( counter > 0 )
{
Thread.Sleep( 1000 ); // Sleep to allow for 1 second timer ticks
Console.WriteLine( "The application will close in {0} seconds.", counter );
Console.CursorLeft = 0;
Console.CursorTop = Console.CursorTop - 1;
counter--;
}
}
DoBackups()
方法构建并执行要针对 master 数据库运行以启动备份的动态 TSQL 脚本。
TSQL 以内联方式包含在代码中,原因有两个。 首先是为了可移植性 - 无需在运行此代码的服务器的 master 数据库中创建存储过程。 其次,SQL 是动态的,因为可以配置应用程序以将日期字符串添加到文件名,也可以不添加。
负责进行备份的 TSQL 是
BACKUP DATABASE @name TO DISK = @fileName
其余的仅用于设置文件路径和日期字符串等变量,并循环遍历服务器上的非系统数据库。
/// <summary>
/// Backs up non system SQL Server databases to the configured directory.
/// </summary>
/// <param name="backupDir"></param>
/// <param name="dateStamp"></param>
private static bool DoBackups( string backupDir, bool dateStamp )
{
bool allBackupsSuccessful = false;
StringBuilder sb = new StringBuilder();
// Build the TSQL statement to run against your databases.
// SQL is coded inline for portability, and to allow the dynamic
// appending of datestrings to file names where configured.
sb.AppendLine( @"DECLARE @name VARCHAR(50) -- database name " );
sb.AppendLine( @"DECLARE @path VARCHAR(256) -- path for backup files " );
sb.AppendLine( @"DECLARE @fileName VARCHAR(256) -- filename for backup " );
sb.AppendLine( @"DECLARE @fileDate VARCHAR(20) -- used for file name " );
sb.AppendLine( @"SET @path = '" + backupDir + "' " );
sb.AppendLine( @"SELECT @fileDate = CONVERT(VARCHAR(20),GETDATE(),112) " );
sb.AppendLine( @"DECLARE db_cursor CURSOR FOR " );
sb.AppendLine( @"SELECT name " );
sb.AppendLine( @"FROM master.dbo.sysdatabases " );
sb.AppendLine( @"WHERE name NOT IN ('master','model','msdb','tempdb') " );
sb.AppendLine( @"OPEN db_cursor " );
sb.AppendLine( @"FETCH NEXT FROM db_cursor INTO @name " );
sb.AppendLine( @"WHILE @@FETCH_STATUS = 0 " );
sb.AppendLine( @"BEGIN " );
if ( dateStamp )
{
sb.AppendLine( @"SET @fileName = @path + @name + '_' + @fileDate + '.bak' " );
}
else
{
sb.AppendLine( @"SET @fileName = @path + @name + '.bak' " );
}
sb.AppendLine( @"BACKUP DATABASE @name TO DISK = @fileName " );
sb.AppendLine( @"FETCH NEXT FROM db_cursor INTO @name " );
sb.AppendLine( @"END " );
sb.AppendLine( @"CLOSE db_cursor " );
sb.AppendLine( @"DEALLOCATE db_cursor; " );
string connectionStr =
ConfigurationManager.AppSettings["DBConnectionString"].ToString();
SqlConnection conn = new SqlConnection( connectionStr );
SqlCommand command = new SqlCommand( sb.ToString(), conn );
try
{
conn.Open();
command.ExecuteNonQuery();
allBackupsSuccessful = true;
}
catch ( Exception ex )
{
Output( "An error occurred while running the backup query: " + ex );
}
finally
{
try
{
conn.Close();
}
catch (Exception ex)
{
Output( "An error occurred while trying to close the database connection:
" + ex );
}
}
return allBackupsSuccessful;
}
在备份之后,如果配置为这样做,程序将检查备份目录中的备份,并查看它们是否足够旧,以至于需要使用文件名中的日期进行删除。 当然,可以使用文件属性计算出文件的年龄,但是这样我们可以保留由其他方式创建的备份,这些备份可能不是您的计划备份的一部分。
/// <summary>
/// Delete back up files in configured directory older than configured days.
/// </summary>
private static void DeleteOldBackups()
{
String[] fileInfoArr = Directory.GetFiles
( ConfigurationSettings.AppSettings["SQLBackupLocation"].ToString() );
for (int i = 0; i < fileInfoArr.Length; i++)
{
bool fileIsOldBackUp = CheckIfFileIsOldBackup( fileInfoArr[i] );
if (fileIsOldBackUp)
{
File.Delete( fileInfoArr[i] );
Output( "Deleting old backup file: " + fileInfoArr[i] );
}
}
}
/// <summary>
/// Parses file name and returns true if file is older than configured days.
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
private static bool CheckIfFileIsOldBackup( string fileName )
{
FileInfo fileInfo = new FileInfo( fileName );
fileName = fileInfo.Name; // Get the file name without the full path
bool backupIsOld = false;
char[] fileNameCharsArray = fileName.ToCharArray();
string dateString = String.Empty;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fileNameCharsArray.Length; i++)
{
if (Char.IsNumber( fileNameCharsArray[i] ))
{
sb.Append( fileNameCharsArray[i] );
}
}
dateString = sb.ToString();
if (!String.IsNullOrEmpty( dateString ))
{
// Delete only if we have exactly 8 digits
if (dateString.Length == 8)
{
string year = String.Empty;
string month = String.Empty;
string day = String.Empty;
year = dateString.Substring( 0, 4 );
month = dateString.Substring( 4, 2 );
day = dateString.Substring( 6, 2 );
DateTime backupDate = new DateTime( int.Parse( year ),
int.Parse( month ), int.Parse( day ) );
int backupConsideredOldAfterDays =
int.Parse( ConfigurationSettings.AppSettings
["DeleteBackupsAfterDays"].ToString() );
// Compare backup date to test if this backup
// should be treated as an old backup.
TimeSpan backupAge = DateTime.Now.Subtract( backupDate );
if (backupAge.Days > backupConsideredOldAfterDays)
{
backupIsOld = true;
}
}
}
return backupIsOld;
}
历史
- 12/12/2009 版本 1 上传
- 13/12/2009 添加了一个安装包,并为代码片段添加了一些进一步的描述
- 23/12/2009 进行了微小的代码调整,将已弃用的
ConfigurationSettings
替换为ConfigurationManager
。 功能没有变化。 稍微澄清了文章文本。