简介:这份资源是面向C# WinForm开发者的数据库备份与恢复实战Demo,基于VS2008实现,适合需要为桌面应用增加数据安全保障能力的初中级开发者参考。资源围绕两种主流方案展开:一是借助SQLDMO这一COM对象模型,通过SQLServer、Database、Backup与Restore对象完成备份与恢复,控制粒度更细、错误处理更完善;二是直接执行BACKUP DATABASE与RESTORE DATABASE等Transact-SQL语句,写法简洁,适合轻量场景。压缩包共27个文件,约582KB,以cs源码、exe可执行程序、resx与resources界面资源、dll与pdb调试文件、txt说明文档及sln解决方案为主,结构完整可直接编译运行。已有501人学习下载。通过研读源码,读者可掌握数据库连接、SQL语句构建、SQLDMO对象调用以及WinForm界面交互等关键环节,并对比两种方案在部署依赖与适用场景上的差异,为实际项目选型提供参考。
1. 一个 WinForm 小工具,把数据库备份恢复这件事从命令行里捞出来
很多做 C# 桌面端的朋友都遇到过这种场景:客户现场部署了一套 WinForm 业务系统,数据库用的是 SQL Server,某天运维打电话说“数据好像不对,能不能回滚到昨天”。这时候你要么远程连上去敲一长串 BACKUP DATABASE 语句,要么打开 SSMS 点半天图形界面。这个 Demo 干的事情很朴素——把数据库备份和恢复这两件事,做成 WinForm 界面上的两个按钮,而且给了两种实现方式:一种走 SQL 语句,一种走 SQL Server 管理对象(SMO)。它适合谁?适合正在做 C# WinForm 业务系统、需要给客户或运维留一个“一键备份/恢复”入口的开发者,也适合想搞清楚 ADO.NET 和 SMO 在备份恢复场景下到底怎么选的人。下面我按“这项目能干什么 → 怎么跑起来 → 核心代码怎么改”的顺序拆一遍。
2. 项目定位与技术选型:为什么是 WinForm + 两种备份方式
2.1 这个 Demo 到底解决什么问题
数据库备份恢复本身不复杂,复杂的是“让不该碰命令行的人也能安全操作”。这个 Demo 的界面通常长这样:上面一个文本框显示当前连接的数据库实例和数据库名,中间两个按钮——“备份”和“恢复”,下面一个日志框输出执行结果。备份时选择保存路径,恢复时选择 .bak 文件。它不追求做成一个通用数据库管理工具,而是聚焦在“单库、单实例、本地或局域网”的轻量场景。
技术栈上,它用的是 .NET Framework 的 WinForm(常见是 4.7.2 或 4.8),数据库访问层给了两条路:
- 方式一:拼 T-SQL,用
SqlCommand执行BACKUP DATABASE和RESTORE DATABASE。 - 方式二:引用
Microsoft.SqlServer.Management.Smo和Microsoft.SqlServer.Management.Common,用对象模型调Backup和Restore类。
为什么给两种?因为实际项目里这两种方式各有翻车点。T-SQL 方式依赖数据库引擎本身的权限和路径可见性——你写的备份路径是服务器视角的路径,不是你这台客户端的路径。SMO 方式则依赖客户端装了对应的 SMO 程序集,版本对不上就报“找不到类型”。这个 Demo 把两条路都摆出来,本质上是让你在“少依赖”和“少写字符串”之间做选择。
2.2 目录结构长什么样
一个典型的这类 Demo 目录不会太深,常见做法是:
DbBackupRestoreDemo/ ├── App.config ├── Program.cs ├── MainForm.cs ├── MainForm.Designer.cs ├── MainForm.resx ├── Services/ │ ├── SqlCommandBackupService.cs │ └── SmoBackupService.cs ├── Models/ │ └── DbConnectionInfo.cs └── Properties/ ├── AssemblyInfo.cs ├── Resources.resx └── Settings.settingsServices下两个类分别对应两种实现,Models里放连接信息(服务器名、数据库名、用户名、密码或集成认证)。App.config里通常放连接字符串和默认备份目录。这种分层的好处是:你后面要换成 MySQL 或 PostgreSQL,只需要替换 Service 层,界面不用动。
2.3 两种方式的依赖差异
| 对比项 | T-SQL 方式 | SMO 方式 |
|---|---|---|
| 需要引用 | 仅System.Data.SqlClient | Microsoft.SqlServer.Management.Smo、ConnectionInfo等 |
| 备份路径视角 | 数据库服务器视角 | 数据库服务器视角(SMO 也是发给服务器执行) |
| 权限要求 | 需要db_backupoperator或更高 | 同上,但对象模型会做一层封装 |
| 版本兼容 | 基本不挑 | SMO 程序集版本要和 SQL Server 版本大致匹配 |
| 代码量 | 字符串拼接多,但直观 | 对象属性清晰,但引用麻烦 |
提示:如果你的项目要发给客户现场,而客户机器上不一定装了 SQL Server 管理工具,SMO 方式可能会因为缺程序集直接崩。这时候 T-SQL 方式反而更稳。
2.4 运行前要准备什么
- 一个可连接的 SQL Server 实例(本地或局域网都行),知道实例名和数据库名。
- 一个用于登录的账号,至少对目标数据库有备份权限。
- 如果走 SMO,需要在项目里通过 NuGet 或手动引用添加
Microsoft.SqlServer.Management.Smo相关程序集。常见做法是引用Microsoft.SqlServer.SqlManagementObjects这个 NuGet 包。 - 备份目录要提前确认:如果你在界面上填
D:\backup\test.bak,这个D:\是数据库服务器那台机器的 D 盘,不是你这台客户端的。这是新手最容易翻车的地方。
3. 核心模块拆解:T-SQL 方式与 SMO 方式的代码实现
3.1 T-SQL 备份:拼字符串但要注意路径和权限
先看备份。T-SQL 方式的核心就是一条BACKUP DATABASE语句,但拼的时候有几个参数必须处理干净。
// SqlCommandBackupService.cs public class SqlCommandBackupService { private readonly string _connectionString; public SqlCommandBackupService(string connectionString) { _connectionString = connectionString; } /// <summary> /// 执行数据库备份 /// </summary> /// <param name="databaseName">要备份的数据库名</param> /// <param name="backupFilePath">备份文件路径(服务器视角)</param> /// <returns>执行结果信息</returns> public string Backup(string databaseName, string backupFilePath) { // 用参数化无法直接传数据库名和路径到 BACKUP 语句, // 所以这里用 QUOTENAME 做一层防护,避免注入和特殊字符问题 string sql = $@" BACKUP DATABASE {QuoteName(databaseName)} TO DISK = @backupPath WITH FORMAT, INIT, NAME = @backupName, STATS = 10"; using (var conn = new SqlConnection(_connectionString)) using (var cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@backupPath", backupFilePath); cmd.Parameters.AddWithValue("@backupName", $"{databaseName}-Full Backup"); cmd.CommandTimeout = 300; // 大库备份可能超过默认 30 秒 conn.Open(); cmd.ExecuteNonQuery(); } return $"备份完成:{backupFilePath}"; } private string QuoteName(string name) { // 简单处理:用方括号包起来,防止数据库名里有空格或关键字 return "[" + name.Replace("]", "]]") + "]"; } }逻辑说明:BACKUP DATABASE后面跟数据库名,TO DISK后面跟路径。路径用参数传,但数据库名没法参数化,所以用QuoteName加方括号。WITH FORMAT, INIT表示覆盖现有备份集并重新初始化,STATS = 10让 SQL Server 每完成 10% 输出一条进度消息——如果你在 WinForm 里想显示进度,可以订阅conn.InfoMessage事件。
参数说明:backupFilePath必须是数据库服务器能访问的路径。如果你在客户端填D:\backup\test.bak,而 SQL Server 服务运行在另一台机器上,它会去那台机器的 D 盘找。常见做法是让用户填服务器上的共享路径,比如\\ServerName\BackupShare\test.bak,并确保 SQL Server 服务账号对这个共享有写权限。
3.2 T-SQL 恢复:比备份多一步“独占”
恢复比备份麻烦,因为恢复时数据库不能被占用。常见做法是先切到 master 库,然后执行RESTORE DATABASE。
public string Restore(string databaseName, string backupFilePath) { // 恢复需要独占,先切到 master,再执行 RESTORE string sql = $@" ALTER DATABASE {QuoteName(databaseName)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE; RESTORE DATABASE {QuoteName(databaseName)} FROM DISK = @backupPath WITH REPLACE, RECOVERY; ALTER DATABASE {QuoteName(databaseName)} SET MULTI_USER;"; using (var conn = new SqlConnection(_connectionString)) using (var cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@backupPath", backupFilePath); cmd.CommandTimeout = 600; conn.Open(); cmd.ExecuteNonQuery(); } return $"恢复完成:{databaseName}"; }逻辑说明:SET SINGLE_USER WITH ROLLBACK IMMEDIATE会强制断开其他连接,这是恢复能成功的关键。WITH REPLACE表示覆盖现有数据库,RECOVERY表示恢复后让数据库可用。最后切回MULTI_USER,否则别人连不上。
参数说明:CommandTimeout设大一点,恢复大库可能几分钟。如果你的连接字符串用的是master库,那没问题;如果连的是目标库本身,恢复时会被踢掉,所以常见做法是连接字符串里Database=master。
3.3 SMO 方式:对象模型更清晰,但引用是门槛
SMO 方式把备份恢复抽象成对象,代码读起来更舒服。
// SmoBackupService.cs using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; public class SmoBackupService { private readonly string _serverName; private readonly string _userName; private readonly string _password; public SmoBackupService(string serverName, string userName, string password) { _serverName = serverName; _userName = userName; _password = password; } public string Backup(string databaseName, string backupFilePath) { var serverConnection = new ServerConnection(_serverName, _userName, _password); var server = new Server(serverConnection); var db = server.Databases[databaseName]; var backup = new Backup { Action = BackupActionType.Database, Database = databaseName, Initialize = true, // 覆盖现有备份集 Checksum = true, // 启用校验和 ContinueAfterError = false }; backup.Devices.AddDevice(backupFilePath, DeviceType.File); backup.SqlBackup(server); return $"SMO 备份完成:{backupFilePath}"; } public string Restore(string databaseName, string backupFilePath) { var serverConnection = new ServerConnection(_serverName, _userName, _password); var server = new Server(serverConnection); var restore = new Restore { Action = RestoreActionType.Database, Database = databaseName, ReplaceDatabase = true, NoRecovery = false }; restore.Devices.AddDevice(backupFilePath, DeviceType.File); restore.SqlRestore(server); return $"SMO 恢复完成:{databaseName}"; } }逻辑说明:Backup对象里Action指定备份类型,Database指定库名,Devices添加备份文件路径。SqlBackup方法真正执行。恢复类似,ReplaceDatabase = true对应 T-SQL 的WITH REPLACE。
参数说明:ServerConnection的构造函数可以传服务器名、用户名、密码。如果走 Windows 集成认证,可以用new ServerConnection(serverName)然后设置LoginSecure = true。SMO 的坑在于程序集版本——如果你引用的 SMO 版本比目标 SQL Server 低太多,某些属性可能不认。
3.4 界面层怎么调这两个 Service
界面层不需要知道底层是 T-SQL 还是 SMO,常见做法是定义一个接口:
public interface IDbBackupService { string Backup(string databaseName, string backupFilePath); string Restore(string databaseName, string backupFilePath); }然后两个 Service 都实现它。MainForm 里根据用户选的单选按钮决定实例化哪个。这样你后面加第三种方式(比如用 PowerShell 调)也不用改界面逻辑。
private IDbBackupService _service; private void btnBackup_Click(object sender, EventArgs e) { try { _service = radioSmo.Checked ? new SmoBackupService(txtServer.Text, txtUser.Text, txtPwd.Text) : (IDbBackupService)new SqlCommandBackupService(BuildConnString()); string result = _service.Backup(txtDatabase.Text, txtBackupPath.Text); AppendLog(result); } catch (Exception ex) { AppendLog("备份失败:" + ex.Message); } }逻辑说明:用接口把两种实现统一,界面只依赖接口。AppendLog往日志框追加文本,方便排查。异常直接捕获并显示,不要让它弹到未处理异常。
参数说明:BuildConnString根据界面上的服务器、用户名、密码拼连接字符串,注意Database=master。如果用户选 SMO,就不需要连接字符串,直接传服务器名和账号。
4. 避坑与常见问题:备份恢复路上最容易翻车的几个点
4.1 备份路径写成了客户端路径
现象:代码执行成功,但去客户端 D 盘找 .bak 文件找不到。原因:BACKUP DATABASE ... TO DISK里的路径是数据库服务器视角的路径,不是你这台运行 WinForm 的机器的路径。解决:要么让用户填服务器上的本地路径,要么用 UNC 共享路径,并确保 SQL Server 服务账号有写权限。我一般会在界面上加一行提示:“请填写数据库服务器上的路径”。
4.2 恢复时报“数据库正在使用”
现象:RESTORE DATABASE执行失败,提示无法获得独占访问。原因:还有别的连接连着目标库。解决:在恢复语句前加ALTER DATABASE ... SET SINGLE_USER WITH ROLLBACK IMMEDIATE,强制踢掉其他连接。注意这会回滚未提交事务,生产环境要谨慎。
4.3 SMO 引用版本对不上
现象:编译通过,运行时报“Could not load file or assembly Microsoft.SqlServer.Management.Smo”或某个属性找不到。原因:项目引用的 SMO 程序集版本和运行环境里的不一致,或者目标机器根本没装。解决:用 NuGet 安装Microsoft.SqlServer.SqlManagementObjects,并把相关 DLL 一起打包到输出目录。如果客户环境不可控,建议默认走 T-SQL 方式。
4.4 备份文件太大导致超时
现象:小库没问题,大库备份到一半报超时。原因:SqlCommand.CommandTimeout默认 30 秒。解决:把CommandTimeout设成 0(不限时)或一个较大的值,比如 600。同时可以在备份语句里加STATS = 10,配合InfoMessage事件给界面一个进度反馈,避免用户以为卡死。
4.5 恢复后数据库还是单用户模式
现象:恢复成功,但别人连不上,提示“数据库处于单用户模式”。原因:恢复语句里切了SINGLE_USER但没切回来,或者恢复过程中出错导致没执行到MULTI_USER。解决:把SET MULTI_USER放在finally块里,或者单独用一条语句确保执行。我一般会在恢复完成后主动查一下sys.databases的user_access_desc字段确认状态。
5. 二次开发与改造:从 Demo 到能交付的小工具
5.1 把连接信息做成可配置
Demo 里连接信息可能是硬编码或手填的。要交付给客户,常见做法是写进App.config的connectionStrings节点,界面上只显示数据库名和备份路径。这样客户不用关心服务器地址,也减少填错概率。
<connectionStrings> <add name="MasterDb" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=master;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings>读取时用ConfigurationManager.ConnectionStrings["MasterDb"].ConnectionString。如果客户环境用 SQL 账号,把Integrated Security换成User ID和Password。
5.2 加一个备份历史记录
光有备份按钮不够,客户会问“上次备份是什么时候”。可以在备份成功后往一张本地表或一个 JSON 文件里追加一条记录:时间、数据库名、文件路径、文件大小。恢复时下拉框列出历史备份,选一个直接恢复。这样工具就从“能用”变成“好用”。
// 简单的历史记录模型 public class BackupRecord { public DateTime BackupTime { get; set; } public string DatabaseName { get; set; } public string FilePath { get; set; } public long FileSizeBytes { get; set; } }序列化用System.Text.Json或Newtonsoft.Json都行。注意文件路径要存服务器视角的路径,恢复时直接拿来用。
5.3 恢复前自动做一次当前库备份
这是血泪经验:用户点恢复之前,你永远不知道他选的那个 .bak 是不是他想要的。我一般会在恢复逻辑里加一步——先对当前数据库做一次临时备份,命名带时间戳,放在临时目录。如果恢复失败,至少还有后悔药。这个操作会增加恢复时间,但能避免“恢复错了就全没了”的事故。
public string RestoreWithSafetyNet(string databaseName, string backupFilePath) { string safetyBackup = Path.Combine( Path.GetTempPath(), $"{databaseName}_safety_{DateTime.Now:yyyyMMddHHmmss}.bak"); // 先备份当前状态 Backup(databaseName, safetyBackup); try { return Restore(databaseName, backupFilePath); } catch { // 恢复失败,尝试回滚到安全备份 Restore(databaseName, safetyBackup); throw; } }逻辑说明:先备份当前库到临时目录,再执行恢复。如果恢复抛异常,用安全备份再恢复回去。参数说明:Path.GetTempPath()在服务器视角下可能不对,实际项目里应该用一个配置好的服务器可写目录。
5.4 验证方法:怎么确认备份文件真的能用
备份完不要只看“执行成功”。常见做法是执行RESTORE VERIFYONLY FROM DISK = @path,让 SQL Server 校验备份集是否完整。这个语句很快,不实际恢复数据,但能发现文件损坏或截断。
RESTORE VERIFYONLY FROM DISK = @backupPath WITH CHECKSUM;在代码里加一个“验证”按钮,调这条语句,返回“备份集有效”才让用户放心。我每次写完备份功能,都会强制走一遍“备份 → 验证 → 恢复 → 查数据”的完整链路,确认没问题才交付。从那以后我每次做数据库工具,都强制走一遍这个链路,不再只看 ExecuteNonQuery 有没有抛异常。希望帮到你。
本文还有配套的精品资源,点击获取