在线还原数据库备份文件
需求
1.用户上传数据库备份文件(.bak)还原到指定服务器上(如果用户不上传文件,则还原默认的备份文件)
2.还原文件后,创建访问该数据库的用户,登录名和密码可由用户输入(如果用户不输入登录名和密码,则生成默认的登录名和密码)
问题
按照需求,很容易想打使用restore database语句,代码实现后测试发现,重复还原一个数据库文件,会提示还原失败,正在使用该文件。
因为还原后在文件夹目录中会有相同的逻辑文件名称,所以会出现问题。
解决方案
使用restore database with move还原语句,将每次还原的文件放在不同的位置
代码
页面代码
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>还原数据库</title> <link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/webuploader/0.1.5/webuploader.css" /> <style> * { padding: 0; margin: 0; } html, body { height: 100%; font-size: 16px; font-family: \'Microsoft YaHei\'; } table { width: 100%; height: 100%; } table td { text-align: center; } td > input { margin: 15px 0; padding: 5px; border: 1px solid #ccc; border-radius: 3px; width: 400px; height: 30px; font-size: 16px; font-family: \'Microsoft YaHei\'; } p { line-height: 50px; color: #999; display: none; } .btn { display: inline-block; text-decoration: none; color: #fff; background-color: #0094ff; padding: 10px 15px; border-radius: 3px; } .btn:hover { opacity: 0.9; } .icofile { display: none; color: green; } .bar { width: 100%; height: 10px; background: #999; position: relative; display: none; } .subbar { height: 10px; background: green; position: absolute; top: 0; left: 0; } .webuploader-pick { border: 1px dashed #ccc; padding: 20px 10px 20px 50px; background: url(http://www.easyicon.net/api/resizeApi.php?id=1160478&size=32) no-repeat 10px center; color: #666; } .tips { color: #999; line-height: 40px; } </style> </head> <body> <table> <tr> <td> <div id="uploader" style="width:400px;margin:0 auto;"> <div class="btns"> <div id="picker">选择备份文件</div> <div class="bar"> <div class="subbar"></div> </div> <div class="filelist"></div> </div> </div> <div class="tips"> 说明:不上传备份文件,将还原默认的数据库备份文件 </div> <input type="text" placeholder="还原后数据库名称" id="name" /><br /> <input type="text" placeholder="数据库账户,可不填写" id="account" /><br /> <input type="password" placeholder="数据库密码,可不填写" id="pwd" /><br /> <a href="javascript:;" id="restore" class="btn">一键还原</a><br /> <p class="tip">还原时间与文件大小成正比,请耐心等待.....</p> </td> </tr> </table> <script src="http://apps.bdimg.com/libs/jquery/1.7.2/jquery.min.js"></script> <script src="https://cdn.staticfile.org/webuploader/0.1.5/webuploader.js"></script> <script> $(function () { //文件上传 var uploader = WebUploader.create({ swf: \'https://cdn.staticfile.org/webuploader/0.1.5/Uploader.swf\', auto: true, server: \'/Home/Upload\', pick: { id: \'#picker\', multiple: false, }, resize: false, fileNumLimit: 1, duplicate: false,//去重 fileSizeLimit: 100 * 1024 * 1024,//100M accept: { title: \'Bak\', extensions: \'bak\', mimeTypes: \'application/x-trash\' } }); //加入队列前 uploader.on(\'beforeFileQueued\', function (file) { var files = uploader.getFiles(); files.forEach(function (item) { uploader.removeFile(item, true); }); }); //加入队列时 uploader.on(\'fileQueued\', function (file) { $(".filelist").html("文件:" + file.name); }); //进度条 uploader.on(\'uploadProgress\', function (file, percentage) { $(".bar").show(); $(".subbar").css("width", percentage * 100 + "%"); }); //成功 uploader.on(\'uploadSuccess\', function (file, response) { $(".bar").fadeOut(2000); if (response.message != "OK") { alert(response.message); } }); //失败 uploader.on(\'uploadError\', function (file, reason) { alert(reason); }); //完成 uploader.on(\'uploadComplete\', function (file) { }); //验证 uploader.on(\'error\', function (msg) { if (msg == "Q_EXCEED_NUM_LIMIT") { alert("最多选择一个文件上传"); } else if (msg == "Q_EXCEED_SIZE_LIMIT") { alert("文件最大不能超过100M"); } else if (msg == "Q_TYPE_DENIED") { alert("文件类型必须是BAK文件"); } else if (msg == "F_DUPLICATE") { alert("队列中有同名文件了"); } else { alert("未知错误:" + msg + ",请联系客服"); } }); //还原数据库文件 $("#restore").click(function () { var valid = true; if ($("#name").val() == "") { valid = false; return false; } if (valid) { $(this).hide(); $(".tip").show(); $.post("/Home/Restore", { name: $("#name").val(), account: $("#account").val(), pwd: $("#pwd").val(), isRestoreDefault: uploader.getFiles().length == 0 }, function (data) { alert(data.msg); window.location.reload(); }); } }); }); </script> </body> </html>
后台代码
[HttpPost] public JsonResult Restore(string name, string account, string pwd, bool isRestoreDefault) { using (SqlConnection connection = new SqlConnection("Data Source=.;uid=" + Config("DBUser") + ";pwd=" + Config("DBPwd") + ";database=master;timeout=180")) { try { connection.Open(); string dbNewUserName = name + "User"; string dbNewUserPwd = name + "User!@#"; if (!string.IsNullOrEmpty(account)) { dbNewUserName = account; dbNewUserPwd = pwd; } //备份文件获取 string path = Server.MapPath("/Content/" + (isRestoreDefault ? "cms" : "temp") + ".bak"); List<string> logicalNameList = new List<string>(); //还原前,获取数据库备份文件的逻辑名称 string cmdText = "restore filelistonly from disk=\'" + path + "\'"; SqlCommand cmd = new SqlCommand(cmdText, connection); cmd.CommandTimeout = Int32.MaxValue;//命令执行时间 SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { logicalNameList.Add(reader["LogicalName"].ToString()); } reader.Close(); //创建数据库 cmdText = @"restore database " + name + " from disk=\'" + path + "\' with"; string dataPath = Server.MapPath("/RestoreData/"); cmdText += " move \'" + logicalNameList[0] + "\' to \'" + dataPath + name + ".mdf\',"; cmdText += " move \'" + logicalNameList[1] + "\' to \'" + dataPath + name + ".ldf\'"; cmd = new SqlCommand(cmdText, connection); cmd.ExecuteNonQuery(); //创建用户 cmd = new SqlCommand("create login " + dbNewUserName + " with password=\'" + dbNewUserPwd + "\', default_database=" + name, connection); cmd.ExecuteNonQuery(); string dbSql = string.Format(@"use {0} create user {1} for login {1} with default_schema=dbo exec sp_addrolemember \'db_owner\', \'{1}\' ", name, dbNewUserName, dbNewUserPwd); cmd = new SqlCommand(dbSql, connection); cmd.ExecuteNonQuery(); //删除上传的数据库文件 if (!isRestoreDefault) { System.IO.File.Delete(path); } return Json(new { msg = "还原成功" }); } catch (Exception e) { return Json(new { msg = "还原失败:" + e.Message }); } finally { connection.Close(); } } }