在这篇文章我们学习用C#来操作,并制作一个备份还原工具。
我们要操作cmd.exe使用到了Process类,使用这个类首先要引入命名空间 System.Diagnostics,此类提供对本地和远程进程的访问并能够启动和停止本地系统进程。
/// <summary> /// 执行Cmd命令 /// </summary> /// <param name="workingDirectory">要启动的进程的目录</param> /// <param name="command">要执行的命令</param> public static void StartCmd(String workingDirectory, String command) { Process p = new Process(); p.StartInfo.FileName = "cmd.exe"; p.StartInfo.WorkingDirectory = workingDirectory; p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardInput = true; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.StartInfo.CreateNoWindow = true; p.Start(); p.StandardInput.WriteLine(command); p.StandardInput.WriteLine("exit"); p.WaitForExit(); p.Close(); }
在看看如何使用上方法:
string appDircectroy="C:\\Program Files\\MySQL\\MySQL Server 5.5\\bin"
string cmd="mysqldump -hlocalhost -uroot -proot --default-character-set=utf8 --lock-tables --routines --force --quick Dbname>d:\backup.sql"
StartCmd(appDircectroy,cmd);
这里我们同Process操作cmd.exe,调用mysqldump.exe 执行命令"-hlocalhost -uroot -proot --default-character-set=utf8 --lock-tables --routines --force --quick Dbname>d:\backup.sql"备份数据库。其实我们也可以直接通过Process类直接调用mysqldump.exe进程执行命令,写法如下:
/// <summary> /// 使用mysqldump执行command命令 /// </summary> /// <param name="AppPath">mysqldump.exe的目录</param> /// <param name="command"></param> public static void StartMySqldump(string AppPath, string command) { ProcessStartInfo psi = new ProcessStartInfo(AppPath + @"\mysqldump.exe"); psi.Arguments = command; psi.UseShellExecute = false; psi.RedirectStandardOutput = true; psi.RedirectStandardInput = true; psi.RedirectStandardError = true; psi.CreateNoWindow = true; Process pro = Process.Start(psi); pro.WaitForExit(); pro.Close(); }
调用的方法和上面的使用基本一致,就是command命令不需要写mysqldump 改为"-hlocalhost -uroot -proot --default-character-set=utf8 --lock-tables --routines --force --quick Dbname>d:\backup.sql" 。
我再这里使用方法1,比较灵活方便,因为我们原因的使用也可以用到此方法执行mysql.exe还原命令,上篇中说到还原时,数据库如何不存在的使用,我们要先创建数据库,再还原,那么就是要执行两天命令,我们可以改一下方法1,使它可以执行多条命令,如下:
/// <summary> /// 执行Cmd命令 /// </summary> /// <param name="workingDirectory">要启动的进程的目录</param> /// <param name="command">要执行的命令</param> public static void StartCmd(String workingDirectory, String[] commands) { Process p = new Process(); p.StartInfo.FileName = "cmd.exe"; p.StartInfo.WorkingDirectory = workingDirectory; p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardInput = true; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.StartInfo.CreateNoWindow = true; p.Start(); foreach (string cmd in commands) { p.StandardInput.WriteLine(cmd); } p.StandardInput.WriteLine("exit"); p.WaitForExit(); p.Close(); }
有了上面的函数,编写一个winform备份还原工具就比较容易实现了,但是现在我要备份300多个数据库,我要生成300多个数据库备份文件,当然我们也可以通过命令将300多个数据库,即所有数据库(包含mysql自带的系统库)备份到一个文件夹。还有一点要注意就是用户体验,假如备份的一个数据库很大,那么直接调用就会吧UI卡死,所以我们要用异步线程来解决此问题,我打算用四个线程同时执行备份,直到300多个数据库执行完毕。
有人可能对线程不熟悉,不用怕,其实.net提供了一个异步线程的封装 BackgroundWorker,用此类可以很简单实现。 我们用到BackgroundWorker提供的两个事件DoWork和RunWorkerCompleted,DoWork中写线程开始要执行的任务,RunWorkerCompleted中写线程执行的任务结束要执行的工作。看看简单的代码示例:
BackgroundWorker backgroundWorker = new BackgroundWorker(); backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork); backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted); backgroundWorker.RunWorkerAsync(remainDbNames); //remainDbName是list<string>类型, 传入的参数,我保存要备份的库的名字,备份一个库就移除一个库
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { List<string> names = e.Argument as List<string>; //获取传入的参数ramainDbName
BackupMany(filePath, names, boolBackdata); //执行备份方法 //e.Result = e.Argument.ToString(); //完成任务时向RunWorkerCompleted方法传递的结果参数
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
// writeText(textResult,true,"完成数据库"+e.Result.ToString()+"备份操作" + Environment.NewLine);
}
使用就是那么简单,下面看看我启用四个线程同时备份:
//只有在备份按钮事件中生成个异步线程即可 for (int i = 0; i < 4; i++) { BackgroundWorker backgroundWorker = new BackgroundWorker(); backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork); backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted); backgroundWorker.RunWorkerAsync(remainDbNames); }
下面看看单个线程具体如何从remainDbName中取数据库的名字执行备份
object lockobject = new object(); //备份时的lock对象
delegate void WriteText(TextBox textbox, bool append, string text); private void writeText(TextBox textbox, bool append, string msg) { if (this.InvokeRequired) { WriteText d = new WriteText(writeText); object[] obj = new object[3]; obj[0] = textbox; obj[1] = append; obj[2] = msg; this.Invoke(d, obj); } else { if (append) textbox.AppendText(msg); else textbox.Text = msg; } }
private void BackupMany(string path, List<string> dbNames, bool backupdata) { string name = null; lock (lockobject) { if (dbNames.Count > 0) { name = dbNames[0]; dbNames.RemoveAt(0); } } if (name != null) { Backup(path, name, backupdata); BackupMany(path, dbNames, backupdata); } else { timer1.Enabled = false; writeText(textResult, true, "全部执行完成!"); } } private void Backup(string path, string databaseName, bool backupdata) { try { writeText(textResult, true, DateTime.Now.ToString() + " 开始数据库" + databaseName + "备份" + Environment.NewLine); //String command = "mysqldump --quick --host=localhost --default-character-set=gb2312 --lock-tables --verbose --force --port=端口号 --user=用户名 --password=密码 数据库名 -r 备份到的地址"; //构建执行的命令 String directory = path + "\\" + databaseName + ".sql"; String command; if (backupdata) { command = string.Format("mysqldump --quick --host={1} --default-character-set={2} --lock-tables --routines --force --port={3} --user={4} --password={5} {6} -r \"{0}\"", directory, host, characterSet, port, user, password, databaseName); } else { command = string.Format("mysqldump --quick --host={1} --default-character-set={2} -d --lock-tables --routines --force --port={3} --user={4} --password={5} {6} -r \"{0}\"", directory, host, characterSet, port, user, password, databaseName); } Cmd.StartCmd(appDirecroty, command); writeText(textResult, true, DateTime.Now.ToString() + @" 数据库已成功备份到 " + directory + " 文件中" + Environment.NewLine); counter++; writeText(textBox1, false, "共" + totlecount + "个文件,已完成" + counter + "个文件"); } catch (Exception ex) { writeText(txtErr, true, DateTime.Now.ToString() + " 数据库" + databaseName + "备份失败!"); } }
writeText是向TextBox写入文本的方法,因为副线程操作主线程生成的控件时,会有线程安全问题,所以要用异步委托。如何你知道这个怎么用,其实还有一个简单点的方法,就是把前程安全检查给关闭掉,在窗体onload中可以加入Control.CheckForIllegalCrossThreadCalls = false; 这句就可以跨线程操作UI而不会报错。
还原的实现和备份的操作差不多,这里就不提了。本想上传个一下代码可是找不到上传的方法。