资讯详情

资讯详情

C# WinForm数据库备份恢复:T-SQL直连与SMO封装选型对比

简介面向 C# WinForm 桌面开发者的数据库备份与恢复示例聚焦 SQL Server 场景下两种可落地的实现思路借助 SQLDMO 组件完成备份与还原以及直接执行 BACKUP DATABASE、RESTORE DATABASE 语句适合需要为管理系统补充数据安全保障能力的学习者与初级开发者参考。压缩包共 27 个文件约 582KB包含 7 个 cs 源码文件、sln 解决方案、exe 可执行程序、txt 说明以及 dll、resx 等配套资源覆盖工程结构、界面交互与核心逻辑便于直接打开运行和对照阅读。已有 502 人学习。示例通过 WinForm 界面串联连接数据库、选择备份路径、执行恢复等环节并保留 Demo 说明可帮助读者理解两种方案在控制粒度、部署依赖和错误处理上的差异为实际项目选型与改造提供可复用的代码骨架。1. 备份恢复这件小事两条路选错一次要赔一个下午给一个 C# WinForm 客户端加数据库备份与恢复功能看着是三个控件的事选路径、点备份、点恢复。我第一次做的时候也这么想结果上线第二周就被真实数据教育了——备份文件老老实实躺在目录里点恢复弹出一句“因为数据库正在使用无法获得独占访问”客户在电话那头等着我在这头翻错误日志。后来把两种实现方式都完整写了一遍才想明白标题里那个“两种方式”不是为了炫技它是两条维护成本完全不同的路一条是自己拼 T-SQL 直连引擎一条是引 SMO 用对象去调。这篇就把这两条路掰开揉碎对比一遍说清各自适合谁、代价在哪、代码长什么样C# WinForm 数据库备份恢复做过的和正要做的都能直接拿走用。2. 两条路的底层差异T-SQL 直连和 SMO 封装到底谁在干活2.1 T-SQL 直连把意图写成字符串交给引擎解析直连方式的做法很朴素SqlConnection连上去SqlCommand执行一句BACKUP DATABASE或RESTORE DATABASE剩下的事全交给数据库引擎。整个链路里C# 只承担三件事——拼语句、传参数、读结果。真正的文件读写、页校验、日志回滚全部发生在数据库服务进程内部客户端连一个字节的备份数据都不碰。这条路的好处是薄。它只用System.Data.SqlClient.NET Framework 自带发布时不用多带任何一个 DLL客户机器上装没装过数据库管理工具都不影响。我在给某公司的内网工具做这功能时最看重的就是这个安装包从 30MB 涨到 32MB 和涨到 38MB运维的接受度完全不一样。代价也很明确——所有东西都得自己拼。库名要加方括号名字里带]还得转义成]]备份路径是字符串常量带单引号就直接语法错误进度反馈要靠解析引擎吐回来的文本消息错误处理只能靠异常里的错误号做分支。SQL Server 把这类失败归到固定错误号上独占访问被拒那条是 3101备份设备打不开是 3201熟手看号就知道该干嘛新手只能把 message 整个弹给用户看。我一般会这么比判断这条路是否合适看你的备份语句有几种变体。如果就一句固定的BACKUP DATABASE ... WITH INIT直连是最省事的选择一旦要根据数据库大小、是否有文件组、是否需要差异备份、是否要校验来动态拼语句字符串拼接的复杂度会指数级往上走那时候就该考虑第二种方式了。2.2 SMO 封装把管理动作抽象成对象和事件SMO 是 SQL Server Management Objects 的缩写核心命名空间是Microsoft.SqlServer.Management.Smo。用起来是另一套心智模型先new Server(...)拿到一个服务器对象再new Backup()建一个备份对象把动作类型、目标库、设备路径、是否覆盖挨个赋给属性最后调SqlBackup(srv)。很多人第一次接触会误以为 SMO 是另一条执行通道速度更快或者更“底层”。实际不是。SMO 在SqlBackup内部做的事情就是把这些属性翻译成一句 T-SQL再通过ServerConnection发给服务器。执行路径和直连完全一样引擎干活的方式没有任何区别唯一的差异是那句 SQL 谁来拼。这一点可以用它自己的方法验证。Backup对象上有Script(srv)调用它会返回一个StringCollection里面就是 SMO 准备发出去的完整脚本。我第一次把这段脚本打印出来看的时候挺意外的——它生成的语句比我自己手写的还啰嗦带了一堆WITH选项和转义处理。从那以后我就不再纠结“哪个性能更好”这种伪命题了两者的差别从来不在性能在谁承担版本适配和字符串安全。SMO 真正值钱的地方在于它替你吃掉了三样东西一是转义库名、路径、文件逻辑名它都帮你处理二是版本差异不同版本实例在RESTORE的选项上有些微差异SMO 会按目标实例的版本生成对应语法三是结构化的进度事件PercentComplete事件参数里直接给一个Percent整数不用你去正则解析文本。代价是依赖变重了。SMO 不是 .NET 自带的东西要引Microsoft.SqlServer.Smo、Microsoft.SqlServer.SmoExtended、Microsoft.SqlServer.ConnectionInfo、Microsoft.SqlServer.Management.Sdk.Sfc这几套版本还得和你要连的实例大致对齐。发布出来的目录会多出几个 MB某些场景下还会遇到程序集加载失败的问题这一条我在第 5 章单独讲。2.3 差异的根子在“谁解析你的意图”也在“谁承担版本适配”把两条路并排放最容易看清的一点是它们共享同一个执行引擎差别全在客户端这一层。直连方式里客户端只做字符串拼接和异常捕获所有语义都写在 SQL 文本里。“每 5% 报一次进度”这件事在直连方式下体现为STATS 5这个选项加InfoMessage事件的正则解析在 SMO 里体现为PercentCompleteNotification 5加一个事件订阅。底层触发的是同一批消息SMO 只是帮你把解析做好、包装成整数。这个认知很重要因为它决定了后面的选型逻辑。如果你评估的维度是“哪个跑得快”答案是没差别别在这上面纠结。真正的评估维度应该是你的发布环境干净不干净、目标实例有几个版本、备份语句需要多少分支、进度反馈要做到什么精度、出错时你要不要给用户友好提示、以后换数据库产品时改动面有多大。这些才是能拉开差距的地方。我自己踩过的一个误区是早期觉得 SMO 更“高级”所以一律用 SMO。结果一个只连单实例、语句三年没变过的小工具硬是背上了四五个 DLL 和一套版本对齐的麻烦每次换台机器部署都要重新确认一遍引用。回过头看这个功能的复杂度根本撑不起这个依赖纯属给自己找事。3. 六个维度逐项对比从部署体积到进度反馈把结论摆桌上3.1 部署与依赖一个零成本一个拖家带口部署这件事在开发机上永远不是问题在客户现场永远是问题。直连方式引用的是框架自带的System.Data.SqlClient编译产物里不会多任何一个第三方 DLL拷贝一个目录过去就能跑甚至不需要管理员权限。SMO 方式则要引四套程序集编译输出目录里会多出若干 DLL如果用 NuGet 包Microsoft.SqlServer.SqlManagementObjects单个包解压后体积也不小。我在一个只能通过内网文件摆渡发布的环境里试过两种方式直连版直接丢一个 exe 加一个 config 就完事SMO 版因为程序集版本和目标机的 GAC 不一致来回折腾了两轮。这不是说 SMO 不好而是说它的部署假设更“富”——它假设你有条件控制程序集版本、有权限写目录、能保证依赖完整。如果你的发布环境是“拷过去双击就能用”这个假设可能不成立。判据很简单安装包体积有硬约束、或者发布链路不可控U 盘、内网摆渡、单文件绿色版优先直连反过来如果本来就有安装包、有 CI 构建流程、能统一管理依赖版本SMO 的这点体积成本可以忽略。3.2 权限与路径备份文件到底写在谁的磁盘上这是两条路共有的坑但表现形式不一样值得单独说。BACKUP DATABASE ... TO DISK D:\backup\x.bak这句话里的路径是数据库服务所在机器的路径不是客户端机器的路径。你在一台笔记本上跑 WinForm连的是另一台服务器上的实例那句D:\backup指的是服务器上的 D 盘。这个认知错位导致的现象是代码没报错备份提示成功但你在本地磁盘上翻遍所有目录就是找不到文件。SMO 一样逃不掉bkp.Devices.AddDevice(path, DeviceType.File)里的 path 同样是服务端路径。区别在于 SMO 在路径不合法时抛出的异常包装层次更多你得往里剥好几层才能看到真正的“拒绝访问”信息排查反而更费劲。权限上两边要求基本一致执行备份的账号需要目标库的备份权限同时数据库服务账户需要对目标目录有写权限。很多人卡在第二步——SQL 登录有db_backupoperator但服务账户对D:\backup没有 NTFS 写权限结果就是一条含糊的“无法打开备份设备”。我在现场排查这个问题的固定动作是先确认路径是服务端路径再确认服务账户是谁最后才怀疑代码。提示如果客户端和服务端不在一台机器上备份路径请老老实实问运维要一个服务端可写的目录不要试图用 UNC 共享路径图省事那会额外引入网络权限和传输稳定性问题。3.3 进度与超时大库面前两条路都会假死进度条是这类 Demo 里最容易做假的地方。小库几十毫秒就完事进度条随便刷一下看不出来一旦库到了几个 G问题就全冒出来了界面卡死、进度条停在 0、跑满 30 秒直接抛超时。超时这条是最典型的。SqlCommand.CommandTimeout默认是 30 秒BACKUP DATABASE是个长事务型操作几分钟到几十分钟都正常30 秒一到客户端就抛Timeout expired可服务器端的备份其实还在跑。这个问题的解法只有一个执行备份恢复的命令必须把CommandTimeout显式设成 0也就是不限时。SMO 那边稍微好一点ServerConnection有自己的StatementTimeout属性但同样不能留默认值。进度反馈的实现在两条路上完全对称。直连方式靠STATS n让引擎每推进 n% 吐一条信息性消息客户端订阅SqlConnection.InfoMessage事件去接SMO 靠PercentCompleteNotification n加PercentComplete事件。底层是同一批消息SMO 帮你把“已处理百分之 10”这类本地化文本解析成了整数。界面假死则是第三个层面的问题和用哪条路无关。SqlBackup是同步阻塞调用ExecuteNonQuery同步版本也会卡住只要它们跑在 UI 线程上界面必然不动。这个必须在架构上解决——把执行放到Task.Run里进度用IProgressint回传UI 线程只负责刷进度条。3.4 兼容与维护换实例、换版本、换数据库时谁更省事这一维度决定了你三个月后还愿不愿意维护这份代码。版本兼容上SMO 的规则是客户端程序集版本必须不低于大致等于目标实例的主版本直连则取决于你写的 SQL 语法。比如WITH COMPRESSION这个备份选项在很老的实例上不支持直连方式下直接就是语法错误你得自己去查实例版本然后分支。SMO 会按目标实例能力生成省掉这层判断。换实例这件事上差别更大。如果客户现场从单实例变成主备两个实例或者从一套环境切到另一套环境SMO 代码基本不用改改连接串就行直连方式要看你的语句有没有写死实例相关的部分比如文件逻辑名、路径、文件组。但反过来如果哪天要换数据库产品直连方式的改动面是“重写几句 SQL”SMO 的改动面是“整套对象模型推倒重来”。这个不对称在选择时就该想清楚你的项目未来三年更可能换数据库产品还是更可能连更多实例把上面几个维度收成一张表结论会直观很多对比维度T-SQL 直连SMO 封装额外依赖无框架自带4 套以上程序集需版本对齐发布体积增量0数 MB语句安全库名/路径需手工转义对象属性赋值自动处理进度反馈STATSInfoMessage正则解析PercentComplete事件直接给整数超时控制必须手动设CommandTimeout 0需设StatementTimeout版本适配自行判断语法差异按目标实例生成错误定位错误号清晰可分支处理异常包装层次多换库成本重写语句推倒重来适合场景单实例、语句固定、绿色发布多实例、批量、需对象管理4. WinForm 落地两个可抄的 Demo 骨架和一条线程红线4.1 界面与线程UI 线程只负责点按钮和刷进度不管选哪条路线程模型必须是同一套。界面上放三个控件备份按钮、恢复按钮、一个ProgressBar。点击事件里只做两件事——校验输入、起一个后台任务。真正的数据库操作在Task.Run里跑进度通过IProgressint回到 UI 线程更新。// 按钮点击只做参数校验和任务启动绝不在 UI 线程上碰数据库 private async void btnBackup_Click(object sender, EventArgs e) { var bakPath txtPath.Text.Trim(); if (string.IsNullOrWhiteSpace(bakPath)) { MessageBox.Show(请先选择备份路径); return; } btnBackup.Enabled false; progressBar1.Value 0; // Progressint 会自动把回调 marshal 回 UI 线程不用手写 Invoke var progress new Progressint(pct progressBar1.Value pct); try { await Task.Run(() BackupByTsqlAsync(connStr, dbName, bakPath, progress, CancellationToken.None)); MessageBox.Show(备份完成); } catch (Exception ex) { // 这里只弹用户能看懂的部分完整堆栈写日志 MessageBox.Show(备份失败 ex.Message); } finally { btnBackup.Enabled true; } }这段代码的关键点在Progressint。它是IProgressT的默认实现内部通过SynchronizationContext把回调投递回创建它的线程也就是 UI 线程。手写Invoke也能work但在Task.Run里跨线程更新控件迟早会翻车尤其是恢复流程里有多段进度更新的时候。CancellationToken我一般会传进去虽然备份恢复这种操作中途取消的实际意义不大——取消客户端命令不等于服务器停止执行——但至少能让界面上的“取消”按钮有个交代避免用户以为程序死了。4.2 方式一T-SQL 直连的备份与恢复实现先看备份。核心是四件事连接串指向master、打开信息性消息回传、CommandTimeout设 0、库名用方括号包好并转义。// 备份STATS 每 5% 回吐一次进度靠 InfoMessage 事件接住 private async Task BackupByTsqlAsync(string masterConnStr, string dbName, string bakPath, IProgressint progress, CancellationToken ct) { using (var conn new SqlConnection(masterConnStr)) // 连接串的 Initial Catalog 必须是 master { await conn.OpenAsync(ct); // 这一步不能省否则 WITH STATS 的进度消息到不了 InfoMessage conn.FireInfoMessageEventOnUserErrors true; conn.InfoMessage (s, e) { foreach (SqlError err in e.Errors) { // 中英文实例的消息格式不同两个分支都要覆盖 var m Regex.Match(err.Message, (?:百分之\s*(\d))|(?:(\d)\s*percent), RegexOptions.IgnoreCase); if (!m.Success) continue; var raw m.Groups[1].Success ? m.Groups[1].Value : m.Groups[2].Value; if (int.TryParse(raw, out int pct)) progress.Report(pct); } }; using (var cmd conn.CreateCommand()) { // 库名不能参数化只能手工加方括号名字里带 ] 的要双写转义 var safeDb [ dbName.Replace(], ]]) ]; cmd.CommandText $BACKUP DATABASE {safeDb} TO DISK path WITH INIT, STATS 5; cmd.Parameters.Add(path, SqlDbType.NVarChar, 400).Value bakPath; cmd.CommandTimeout 0; // 默认 30 秒大库必翻车必须置 0 await cmd.ExecuteNonQueryAsync(ct); } } }几个参数值得单独说。path用参数传而不是拼字符串是因为路径里很可能出现单引号或反斜杠拼接早晚出事WITH INIT表示覆盖同名文件不加的话遇到已存在的备份文件会直接报错STATS 5里的 5 是百分比粒度库大的话调成 10 能让日志少刷一点库小的话调成 1 进度更细腻。CommandTimeout 0是本方法里最重要的一行没有它其他都白搭。恢复要复杂一点因为它涉及独占访问。正确顺序是先把库置为单用户并把现有连接踢掉再执行RESTORE最后恢复多用户。这三步要放在同一个命令批次里中间失败也得保证库最终回到多用户状态。// 恢复单用户 - RESTORE - 多用户三步顺序错了就是 3101 报错 private async Task RestoreByTsqlAsync(string masterConnStr, string dbName, string bakPath, IProgressint progress, CancellationToken ct) { using (var conn new SqlConnection(masterConnStr)) { await conn.OpenAsync(ct); conn.FireInfoMessageEventOnUserErrors true; // 进度订阅逻辑同备份此处省略 var safeDb [ dbName.Replace(], ]]) ]; using (var cmd conn.CreateCommand()) { cmd.CommandTimeout 0; cmd.CommandText $ IF DB_ID(db) IS NOT NULL ALTER DATABASE {safeDb} SET SINGLE_USER WITH ROLLBACK IMMEDIATE; RESTORE DATABASE {safeDb} FROM DISK path WITH REPLACE, RECOVERY, STATS 5;; cmd.Parameters.Add(db, SqlDbType.NVarChar, 128).Value dbName; cmd.Parameters.Add(path, SqlDbType.NVarChar, 400).Value bakPath; try { await cmd.ExecuteNonQueryAsync(ct); } finally { // 即使 RESTORE 失败也要把库拉回多用户否则业务连不上 using (var fix conn.CreateCommand()) { fix.CommandTimeout 0; fix.CommandText $IF DB_ID(db) IS NOT NULL ALTER DATABASE {safeDb} SET MULTI_USER;; fix.Parameters.Add(db, SqlDbType.NVarChar, 128).Value dbName; await fix.ExecuteNonQueryAsync(CancellationToken.None); } } } } }WITH ROLLBACK IMMEDIATE是踢掉现有连接的关键不带它的话SET SINGLE_USER会一直等等到超时也不一定成功。WITH REPLACE允许覆盖同名库WITH RECOVERY表示恢复完成后立即可用——如果你想做“先恢复、后手动上线”的两段式可以改成WITH NORECOVERY。finally里那段补偿是我加了两次才加上的第一次没加测试时故意让恢复失败结果库卡在单用户状态整个应用连不上只能进管理工具手动改回来。4.3 方式二SMO 的备份与恢复实现SMO 的代码读起来更“对象化”但要注意它的调用是同步的得包在Task.Run里。// SMO 备份属性化配置进度事件直接给百分比 private void BackupBySmo(string masterConnStr, string dbName, string bakPath, IProgressint progress) { var srv new Server(new ServerConnection(new SqlConnection(masterConnStr))); var bkp new Backup { Action BackupActionType.Database, Database dbName, Initialize true, // 等价于 T-SQL 的 WITH INIT Checksum true, // 备份时算校验和恢复时可验证 PercentCompleteNotification 5 // 每 5% 抛一次事件 }; bkp.Devices.AddDevice(bakPath, DeviceType.File); bkp.PercentComplete (s, e) progress.Report(e.Percent); bkp.SqlBackup(srv); // 同步阻塞调用方必须放在后台线程 }属性含义对着看很直观Action决定是整库备份还是日志备份Initialize对应INITChecksum对应WITH CHECKSUMPercentCompleteNotification对应STATS。想确认它到底发了什么语句就在SqlBackup之前插一句var script bkp.Script(srv);断点看一眼script[0]就全明白了比查文档快。恢复那边SMO 提供了KillAllProcesses省掉手写单用户那一段。// SMO 恢复先杀连接再恢复省掉手写 SET SINGLE_USER private void RestoreBySmo(string masterConnStr, string dbName, string bakPath, IProgressint progress) { var srv new Server(new ServerConnection(new SqlConnection(masterConnStr))); try { srv.KillAllProcesses(dbName); } // 库不存在时这里会抛必须包起来 catch { /* 新建库场景忽略 */ } var res new Restore { Database dbName, ReplaceDatabase true, // 等价于 WITH REPLACE PercentCompleteNotification 5 }; res.Devices.AddDevice(bakPath, DeviceType.File); res.PercentComplete (s, e) progress.Report(e.Percent); res.SqlRestore(srv); }注意KillAllProcesses的作用域是这台实例上的这个库它比SET SINGLE_USER WITH ROLLBACK IMMEDIATE温和一点但也不是万能的——如果有连接在持续重连杀完立刻又回来。所以我在实际项目里两边都留着SMO 负责常规清理真遇到顽固连接还是得回到 T-SQL 那条路手动ROLLBACK IMMEDIATE。4.4 恢复前的三件必做事断连接、清池、置单用户不管走哪条路恢复之前有三件事必须确认缺一件就可能撞上 3101。第一件是断开应用自身的连接。你的 WinForm 程序自己就连着这个库恢复时它就是一个占用者。做法是在恢复流程开始前把业务连接全部Close并Dispose不要让它们挂在池子里。第二件是清连接池。SqlConnection.Close()只是把连接还给池子物理连接还在。必须调SqlConnection.ClearAllPools()或ClearPool(conn)才能真正释放。这一步最容易被忽略因为从代码上看连接已经关了。第三件才是置单用户。顺序不能颠倒先清池再置单用户否则你会发现池子里的连接一直在跟你抢。注意ClearAllPools()是进程级的会把所有连接池都清掉。如果你的程序同时连着多个库恢复期间其他库的请求会短暂变慢这是正常代价不用当成 bug 查。5. 避坑与排查五个把我按在地上摩擦的坑5.1 恢复报“数据库正在使用无法获得独占访问”现象点恢复弹出错误号 3101 的提示内容大意是数据库正在使用无法获得独占访问权。重试几次结果一样重启程序也没用。原因有三个层次的占用叠在一起。最外层是应用自己的连接池没清中间层是别的客户端管理工具、报表服务、定时任务还连着最内层是SET SINGLE_USER没带ROLLBACK IMMEDIATE它只是把库标成单用户然后干等。解决恢复流程改成固定三步——先SqlConnection.ClearAllPools()再执行ALTER DATABASE [x] SET SINGLE_USER WITH ROLLBACK IMMEDIATE最后RESTORE。ROLLBACK IMMEDIATE会主动回滚并断开现有事务比默认的等待模式可靠得多。真遇到杀不掉的去查sys.dm_exec_sessions看是谁连着通常是一个忘了关的定时任务。5.2 备份提示成功服务器上却找不到 .bak 文件现象进度条跑完弹窗提示备份完成去本地磁盘对应目录一看空的。原因TO DISK里的路径是数据库服务所在机器的路径。客户端和服务端不在一台机器时文件写在服务器上本地当然找不到。这个坑我在两个不同项目里各踩过一次。解决先确认实例是本机还是远程的。本机的路径随便写远程的让运维给一个服务端可写目录恢复时也用同一个路径。代码里可以在备份成功后用RESTORE HEADERONLY或RESTORE FILELISTONLY读一下备份文件把里面的信息展示给用户也算间接验证文件确实存在。5.3 大库备份跑到 30 秒就抛超时现象小库备份一切正常换成一个几 G 的库必定在 30 秒左右抛Timeout expired可服务器上的备份任务其实还在跑最后也能生成完整文件。原因SqlCommand.CommandTimeout默认 30 秒。备份是个长操作30 秒根本不够。SMO 那边是ServerConnection.StatementTimeout默认值同样不适合长操作。解决执行备份恢复的命令一律设CommandTimeout 0SMO 侧设StatementTimeout 0。这条要写进代码规范不能靠记性我后来是把备份恢复统一封进一个 helper超时值在里面写死外面调用的人根本碰不到这个参数。5.4 恢复完程序连不上库名对了登录映射没对现象恢复成功程序启动报“无法打开登录所请求的数据库”或者能连上但查不到数据。原因备份文件里带的是源库的用户和权限映射恢复到目标实例后原来的 SQL 登录在这台实例上不存在就会变成孤立用户。另外连接串里如果Initial Catalog指的是旧库名恢复后库名变了也会连不上。解决恢复后对目标库执行一次ALTER USER [用户名] WITH LOGIN [用户名]把映射补回来或者干脆用sp_change_users_login系列排查孤立用户。连接串那边恢复流程结束后要让用户重启应用或主动重建连接别指望原来的连接对象能自适应。5.5 SMO 编译通过、运行报找不到程序集现象开发机上跑得好好的拷到客户机双击就崩异常信息里带FileNotFoundException或Could not load file or assembly指向 SMO 的某个 DLL。原因SMO 的程序集版本和目标机上的 GAC 或已装组件版本对不上。开发机装过完整的数据库管理工具GAC 里有对应版本客户机没装只能靠输出目录里的 DLL版本一旦错位就加载失败。解决两条路选一条——要么把依赖的 DLL 全部设为“复制到本地”并确保版本一致要么在app.config里加bindingRedirect把版本重定向到实际存在的那个。前者更省心代价是输出目录变脏。如果客户环境完全不可控回到直连方式反而更稳。6. 选型结论小工具走直连多实例走 SMO外加一条兜底习惯把前面的对比收一收我的结论是这样的单实例、备份语句固定、发布环境不可控绿色包、U 盘、内网摆渡、对安装包体积敏感——选 T-SQL 直连。它零依赖、错误号清晰、出问题好定位唯一要自己写的就是进度解析和转义而这两块代码写一次就能复用到底。多实例、需要批量备份、要做差异备份或日志备份、需要读取数据库对象信息做辅助判断、发布链路可控——选 SMO。它把版本适配和字符串安全都吃掉了代码可读性也更好代价是几个 DLL 和一套版本对齐的规矩。两者混用也完全正常我自己现在就是这么干的备份走直连快、轻、够用恢复走 SMOKillAllProcesses省事遇到 SMO 处理不了的顽固占用再回落到 T-SQL 手动置单用户。这不是骑墙是因为这两件事的失败模式不一样恢复路径更需要容错。不管选哪条有三件事我建议写进代码模板里固定下来备份恢复命令的CommandTimeout一律为 0恢复流程固定为“清池 → 置单用户 → 恢复 → 拉回多用户”最后一步放在finally里备份路径统一走服务端可写目录界面上明确标注“此处填写数据库服务器上的路径”。最后说个习惯。我早期做这类功能喜欢在开发机上用小库一路测到底全绿就交付。后来有一次现场恢复失败库卡在单用户状态业务停了四十分钟从那以后我每次写备份恢复都强制走一遍三个场景用一个几 G 的库测超时、故意把恢复路径写成不存在测异常分支、恢复前多开一个查询窗口占着连接测独占访问。这三个场景跑通才算这个功能真的做完了。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →