MySQL数据库备份工具






4.90/5 (13投票s)
一个用于生成 MySQL 数据库完整备份的类。
引言
论坛上最常见的问题之一是如何直接从 .NET 应用程序备份 MySQL 数据库。不幸的是,与 Access、SqlLite 等不同,它并不是简单地复制和重命名文件,你必须深入研究,基本上从头开始重新创建所有表的 SQL 以及将所有数据插入回表的 SQL。当然,有一些应用程序可以为你完成这项工作,例如 SqlDump、mysqlhotcopy 等,但这些需要对 MySQL 和关系型数据库有一定了解。不幸的是,根据我的经验,99% 的客户甚至不知道什么是关系型数据库;他们只是想要一个可以开具发票、管理库存并带有漂亮图表的计算机程序。当涉及到备份时,他们只希望按下一个写着“备份”的按钮,然后一切就都搞定了。对此最明显的答案是使用 Process.Start("mysqldump", filename)
,然后瞧,你就有一个完整的备份了。遗憾的是,我从未对此取得过太大成功。我告诉你。我尝试过,但它总是会搞砸,特别是当客户在没有其他人帮助他们恢复过程的情况下尝试备份时。然后你就会面对一个愤怒的客户,他们会告诉你,你的编程技能就像一个傻瓜,甚至都不能做出一个可用的备份系统。所以我着手制作了一个防弹的、完全托管的备份类,可以在我的 .NET 应用程序中使用。
我只是觉得,在我完成了大部分预定目标之后,我会与所有需要类似东西的人分享这些努力的成果。不过,首先我需要强调的是,我绝不是一个数据库专家,我只是一个普通的应用程序开发人员,建立了一个构建自定义业务应用程序的企业,这些应用程序恰好有一个数据库作为后端。虽然我已经在自己的数据库上成功测试了它,但这些数据库往往很小,并且位于执行备份的机器上。对于那里的数据库专家,我非常感谢您的评论和批评,以优化它并纠正我做错的事情。如果代码行太长以至于需要滚动才能阅读,我还需要提前道歉。我有宽屏显示器,我讨厌代码中的行继续符(也许除了强调 SQL - 也许),所以就这样吧。
必备组件
除了你需要访问一个带有数据库的 MySQL 服务器,以及可以在这里找到的 MySQL .NET 连接器:MySql Connector for .NET 之外,你只需要 Visual Studio(我用的是 VS 2008)来运行项目和测试备份。
项目设计
显然,一旦你确定了项目的目标,第一件事就是弄清楚实现这些目标需要什么。当涉及到备份数据库模式时,唯一需要决定的事情是
1. 从头开始重建数据库的最佳方法是什么?
经过研究,主要研究了其他人和其他组织是如何实现这一点的,我得出的结论是,为每个表的 CREATE
和 INSERT
重新创建 SQL 将是最明显的方法,尤其因为这在我们的最初结论中。我们还需要决定在重新创建数据库方面需要走多远。我们需要备份存储过程、函数和视图吗?重新创建索引怎么样?还有外键呢?
我决定最好从一开始就以能够重新创建任何数据库为目标,包括存储过程、函数、视图和外键。至于索引,我将依赖 MySQL 本身在恢复时重新创建表时自动创建唯一、主键和外键的索引。任何其他索引都必须在恢复完成后由系统管理员手动创建。
2. 存储备份创建的 SQL 语句的最佳方式是什么?
这里最明显的选择是一个简单的文本文件,它可以作为流读取,并“即时”解析 SQL。但这引发了如何分离不同部分(例如表、存储过程等)的问题,因为你需要使用正则表达式,或某种逗号分隔符或类似的东西来找出你在恢复过程中的位置。此选项还导致了有关如何确保数据完整性以及如何验证所选用于恢复的备份文件的问题。
我想到的解决方案是使用 XML 文件格式。这解决了上面所有问题,因为你可以轻松地使用 XML 标签将各个部分分开,而且使用 XML 属性,不仅可以恢复整个数据库,还可以只恢复你需要的部分。这个解决方案也解决了所有验证问题,因为你可以创建一个 XML 模式,即使在用户选择文件时,也可以用来验证整个文件。由于我正在使用 VB.NET 创建该类,我还可以使用 LINQ 和 XML 字面量(抱歉 C# 开发者 - 你们要等到 .NET 5.0)相对轻松地创建和读取 XML 文件。非常感谢 Jorge Paulino 的 XML 字面量文章,因为我之前从未听说过它们。这种方法的另一个优点是,由于 XML 是一个国际标准,即使没有创建该文件的应用程序,重新创建数据库也相对容易。即使只是用记事本打开,也很容易看到数据库的结构和数据。
3. 如何实际创建 SQL 并将其保存到 XML 文件中?
主要目标之一是确保我们可以在所有数据库项目中重用此代码,因此唯一的方法是创建一个独立的类库,可以根据需要导入到任何其他项目中。问题就变成了是使用一个处理备份和恢复的单个类,还是使用两个类,一个用于备份,一个用于恢复。我选择了两个独立的类,主要是因为这样每个类都可以有相同的构造函数,并且仍然知道它是恢复还是备份。我包含了一个处理 XML 创建和验证的模块。该模块还包括一个验证与服务器连接的函数,以及一个返回特定用户有权查看所选服务器上数据库列表的函数。
BackUp 类
工作原理
BackUp
类有三个私有变量:一个 ConnectionStringBuilder
用于保存服务器和用户信息,以及在各种备份例程中构造连接字符串;一个 XDocument
(它只是一个 XML 文件的内存表示),用于构造备份文件(我随意地给这些文件分配了 .msb 扩展名 [MySQL Backup - 我认为这很有创意]);以及一个字符串,用于保存将要保存的备份文件的完整路径名。该类有一个构造函数和一个公开方法 - BackUpDataBase
(我的原创性又来了)。构造函数接受四个字符串作为参数,即 UserName
、Password
、DataBase
和 FileName
,它们都相当不言自明。它使用这些参数来实例化 ConnectionStringBuilder
并分配路径名。构造函数还会调用验证模块中的一个方法 BuildNewBackUpFile
,该方法创建一个全新的 .msb 文件。此方法使用 XML 字面量构建一个 XDocument
,然后返回该文档。
Friend Function BuildNewBackUpFile() As XDocument
'You just gotta love these XML literals dontcha
Dim NewXDoc As New XDocument wXDoc = <?xml version="1.0" encoding="UTF-8"?>
<!--MySql Backup File-->
<DataBase>
<Tables></Tables>
<Constraints></Constraints>
<Inserts></Inserts>
<Views></Views>
<Procedures></Procedures>
<Functions></Functions>
<Events></Events>
<Triggers></Triggers>
</DataBase>
Return NewXDoc 'Easy as that
End Function 'BuildNewBackUpFile
正如您从例程中看到的那样,它的布局非常简单。然后各个部分将按以下方式保存到相应的标签下:
<Tables>
<Table Name="MyTableName">CREATE TABLE `Table1`(`ID`int(10),`Name`varchar(45))</Table>
<Table Name="MyTableName2">CREATE TABLE`Table2`(`ID`int(10),`Name`varchar(45))</Table>
<Tables>
<Constraints>
<Constraint Name="MyConstraint">ALTER TABLE `MyTable` _
ADD CONSTRAINT `MyConstraint`FOREIGN KEY (... etc <Constraint>
<Constraints>
BackUpDataBase 例程
公开方法 BackUpDataBase
接受一个可选的 BackGroundWorker
作为参数,这使得在备份过程中报告进度变得容易。基本上,这个例程所做的就是按顺序一个接一个地调用每个单独的部分,不同的步骤已经被分解成各自的例程。我不会用每个例程的每个细节来烦扰你,它们都非常相似,所以我将介绍两个最重要的例程:BackUpTables
(CreateTable
是 BackUpTables
的一个分支)和 BackUpData
。
BackUpTables 例程
这个例程基本上使用 Information_Schema 数据库查询属于所选数据库的所有表的名称,然后使用 DataReader
遍历这些表,调用 CreateTable
例程来构建创建每个表的 SQL。一旦 CREATE TABLE
语句返回,该例程就会创建一个新的 XElement
来保存新的 XML,将表名分配给 Table
标签的 Name
属性,并将 XElement
添加到父节点 Tables
下。
Dim newElement As New XElement("Table", strCreateTable)
newElement.@Name = strTableName 'Set name attribute to Table Name
Dim parent As XElement = msbBackUp...<Tables>.FirstOrDefault
parent.Add(newElement) 'Add to Tables
然后,它对每个表调用 BackUpData
例程,为表中数据的每一行创建 Insert
语句。使用相同的查询可以得到引擎类型和 Next
Auto_Increment
值,以附加到 CREATE TABLE
语句的末尾。由于这是最耗时的阶段,该例程可以选择将 BackGroundWorker
作为参数,允许工作器在进行过程中报告进度。只有当工作器存在时,才会在 Information_Schema.Tables 表上通过 Count
查询计算此值。
CreateTable 例程
此例程接受表名、引擎类型和 Next
Auto_Increment
作为参数,并为每个表构建并返回完整的 CREATE TABLE
语句。最初,它开始创建 DROP TABLE
语句,然后使用 DESCRIBE TABLE
查询来构建 CREATE TABLE
语句,迭代返回的每个字段。
Dim strReturn As String = "DROP TABLE IF EXISTS _
`" & strConnection.Database & "`.`" & Table & "`;"
Using conDetails As New MySqlConnection(strConnection.ToString)
strReturn &= "CREATE TABLE `" & strConnection.Database & "`.`" & Table & "` ("
Dim cmdRows As New MySqlCommand("DESCRIBE `" & Table & "`", conDetails)
Dim dbrRows As MySqlDataReader
conDetails.Open()
dbrRows = cmdRows.ExecuteReader()
查询 Describe Table
返回表中每列的六个值:Field
(名称)、Type
(数据类型)、Null
、Key
、Default
和 Extra
。收到这些信息后,只需按此顺序构建每列:名称 数据类型 (最大值) NOT NULL / NULL AUTO_INCREMENT
。我们会在进行过程中单独构建一个主键,如果不为空,则将其附加到 Create Table
。最后,我们将在语句末尾包含有关引擎类型和 Next Auto_Increment
编号的信息,以确保恢复创建正确类型的表(如果未指定,MySQL 默认为 InnoDB)。
While dbrRows.Read() ' For each Field in table
strReturn &= "`" & dbrRows.GetString("Field") & "` " & dbrRows.GetString("Type")
If Not dbrRows.GetString("Null") = "YES" Then
strReturn &= " NOT NULL"
End If
If Not IsDBNull(dbrRows.Item("Default")) Then
strReturn &= " DEFAULT '" & dbrRows.GetString("Default") & "'"
End If
If Not dbrRows.GetString("Extra") = Nothing Then
strReturn &= " " & dbrRows.GetString("Extra").ToUpper()
End If
If Not dbrRows.GetString("Key") = Nothing Then
If dbrRows.GetString("Key") = "PRI" Then
If strPrimaryBuilder = String.Empty Then
strPrimaryBuilder = dbrRows.GetString("Field")
Else
strPrimaryBuilder &= "," & dbrRows.GetString("Field")
End If
End If
End If
strReturn &= "," 'add comma between Fields
End While
If strPrimaryBuilder = String.Empty Then
strReturn = strReturn.Remove(strReturn.Length - 1, 1)
Else
strPrimaryKey = "PRIMARY KEY (" & strPrimaryBuilder & ")"
End If
strReturn &= strPrimaryKey & ") "
dbrRows.Close()
strReturn &= "ENGINE=" & TableEngine
If AutoIncrement >= 0 Then
strReturn &= " AUTO_INCREMENT=" & AutoIncrement
BackUpData 例程
这部分可能是所有工作的骨干,但我们所做的只是从表中调用一个 SELECT *
查询,将其传递给读取器,然后用结果构建一个 Insert
SQL 语句。
Dim strInsert As String = String.Empty
Using conData As New MySqlConnection(strConnection.ToString)
Dim cmdRows As New MySqlCommand("SELECT * FROM `" & _
strConnection.Database & "`.`" _
& Table & "`", conData)
conData.Open()
Dim dbrRows As MySqlDataReader = cmdRows.ExecuteReader
If dbrRows.HasRows Then 'If no Data ...bugger off and get a coffee
我发现必须检索每个值的数据类型,然后单独处理许多数据类型,因为字符串中的撇号、MySQL/.NET 日期转换等问题可能会突然阻碍进程。让我惊讶的一件事是需要将布尔值转换为整数,因为在保存布尔值时,它会字面上保存为 True 或 False,然后尝试恢复时,它会显示一些关于无法转换为布尔值的错误。我需要解决的一个重要问题是如何保存 Blob 列。最后,我通过将字节检索到字节数组中,然后将字节转换为十六进制,然后存储十六进制版本来解决了这个问题。这适用于图片、Word、Excel 和文本文件,所有文件都能完美恢复(Excel 会发出关于文件格式错误或其他内容的警告,但如果你忽略它并让它继续,它会正常打开)。由于我不知道 Blob 字段的大小,我不得不使用一个任意值(我将其限制为 1 MB - 任意)初始化字节数组。如果返回的文档或图片的大小可能大于此,则必须增加此值。这还严重依赖于您的服务器能够作为结果集返回的数据的最大大小,MySQL 默认也将其设置为 1 MB。在我专门创建的测试数据库上进行了一些测试,在开始出现内存不足异常(在具有 2 GB 内存的基本 32 位系统上)之前,我可以将其提高到 15 MB。您在表中拥有的记录越多,这显然会变得越低,请记住所有这些都进入了一个内存中的 XML 文件,因此您可以很容易地看到内存将很快被用完。
While dbrRows.Read
strInsert = "INSERT INTO `" & conData.Database & "`.`" & Table & "` ("
Dim intFieldCount As Integer = dbrRows.FieldCount - 1
Dim columns As New List(Of String)
Dim values As New List(Of String)
For intColumns As Integer = 0 To intFieldCount
If Not IsDBNull(dbrRows(intColumns)) Then
columns.Add("`" & dbrRows.GetName(intColumns) & "`")
Dim strType As String = dbrRows.Item(intColumns).GetType.ToString
Select Case strType
Case "System.DateTime" l
Dim dteValue As DateTime = dbrRows.GetMySqlDateTime(intColumns)
Dim strValue As String = "'" & dteValue.Year & "-" & _
dteValue.Month & "-" & _
dteValue.Day & "'"
values.Add(strValue)
Case "System.Boolean"
Dim intBoolean As Integer
If dbrRows.Item(intColumns) = True Then
intBoolean = 1
Else
intBoolean = 0
End If
values.Add(intBoolean.ToString)
Case "System.String"
Dim strValue As String = dbrRows.GetString(intColumns)
strValue = strValue.Replace("'", "''")
strValue = strValue.Replace(";", "")
values.Add("'" & strValue & "'")
Case "System.Byte[]"
Dim bytBlob(1048576) As Byte
Dim lngFileLength As Long = _
dbrRows.GetBytes(intColumns, 0, bytBlob, 0, 1048576)
ReDim Preserve bytBlob(lngFileLength)
values.Add("0x" & ByteArrayToHex(bytBlob))
Case Else 'Otherwise
If IsNumeric(dbrRows.Item(intColumns)) Then
values.Add(dbrRows.Item(intColumns))
Else
values.Add("'" & dbrRows.Item(intColumns).ToString & "'")
End If
End Select
End If
Next
strInsert &= Join(columns.ToArray, ", ") & ") " & "VALUES ( "
strInsert &= Join(values.ToArray, ", ") & " )"
一旦 INSERT
语句完成,该例程将继续在 <Inserts>
标签下创建一个 XML 元素,其中包含 <Insert>
标签,如上所述。
备份约束、视图、过程和函数
上述每个部分都有自己的例程,例如,BackUpConstraints
、BackUpViews
等,每个例程的性质都非常相似。基本上,它们都查询 Information_Schema 数据库以检索与当前数据库相关的实体名称,然后遍历每个找到的值并根据情况调用 SHOW CREATE FUNCTION
或 SHOW CREATE PROCEDURE
等。然后将该调用的结果传递给一个读取器,该读取器从正确的列中检索值并构建一个 XML 元素,然后将其添加到当前的 XDocument
中。
完成备份
一旦最初的 BackUpDataBase
例程完成了所有部分,只需调用 XDocument
的 Save
方法即可完成备份。
Restore 类
工作原理
Restore
类只有两个私有变量,即 ConnectionStringBuilder
和 XDocument
。该类的构造函数接受五个参数:用户名、密码、服务器、数据库和要恢复的文件的完整路径。与 BackUp
类一样,构造函数使用这些参数实例化 ConnectionStringBuilder
并使用 XDocument.Load(FileName)
方法将 XDocument
变量设置为正确的文件。Restore
类还有另外两个公共方法,一个用于验证所选备份文件是否包含与所选数据库相关的信息,另一个方法执行所有工作:RestoreDataBase
(再次,额外的原创性得分)。
ValidateDataBases
我包含了这个例程,以确保用户为正确的数据库选择了正确的备份文件。它所做的只是检查附加到主数据库标签的 Name
属性是否与所选数据库的名称匹配。使用 XML 字面量,这变得非常容易。
Public Function ValidateDataBases() As Boolean
'Just checks whether the correct backup file for selected database are the same
Dim strDataBaseName As String = msbRestoreFile...<DataBase>.@Name
If strDataBaseName = strConnection.Database Then
Return True
Else
Return False
End If
End Function
此函数需要在应用程序中主调用 RestoreDatabase
之前调用,该应用程序用于执行实际恢复,如下文我在演示应用程序中介绍时将解释。
RestoreDataBase
此例程是整个类的核心。它接受一个可选的 BackGroundWorker
作为参数,以方便进度报告。基本上,此例程只调用实际恢复不同相关部分的例程。它首先恢复表,然后重新创建所有外键和唯一键约束,最后恢复实际数据。通过这种方式,我认为数据在恢复时将针对所有约束进行验证。从那时起,它会重新创建所有函数、过程等(如果存在)。由于所有不同的部分都非常相似,我将只介绍 RestoreTables
和 RestoreData
例程。
RestoreTables
对于所有这些例程,我们只是使用 LINQ 和 XML 字面量从备份文件的相关部分提取每个值,然后针对数据库执行该值(它是一个完全形成的 SQL 语句)。在 RestoreTables
例程中,您可以看到我首先关闭了所有约束检查,这样我们就不会遇到删除具有外键约束的表的问题。使用 XML 字面量和 LINQ 检索值的好处是,如果没有值,什么都不会发生。您不会收到任何错误或警告或任何东西,它只是继续到下一部分(尽管我不太明白为什么会有人备份一个没有表的数据库)。
Private Sub RestoreTables()
Using conTables As New MySqlConnection(strConnection.ToString)
conTables.Open()
Dim cmdSetUp As New MySqlCommand(" _
SET _
@OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;"&" _
SET _
@OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, _
FOREIGN_KEY_CHECKS=0;", conTables)
cmdSetUp.ExecuteNonQuery()
For Each table In From element In msbRestoreFile...<Table>
Dim cmdCreateTable As New MySqlCommand(table.Value, conTables)
cmdCreateTable.ExecuteNonQuery()
Next
End Using
End Sub 'RestoreTables
这是所有 Restore 例程(RestoreConstraints
、RestoreViews
等)遵循的基本过程,除了 RestoreData
例程,我使用 MySqlTransaction
来执行 Insert
s,因为这样,如果出现问题,总是有回滚的选项,而且速度也快得多。在所有其他情况下使用事务是适得其反的,因为在执行 CREATE TABLE
、CREATE FUNCTION
等语句时,MySQL 会隐式提交,因此事务将是多余的。
RestoreData
我让这个例程接受一个可选的 BackGroundWorker
作为参数,以便我们可以在进行过程中报告进度。除了计算我们在过程中走了多远的一些代码之外,其他一切都与之前发生的事情非常相似,只是我们使用 Try
、Catch
、End Try
块来捕获任何异常,如果捕获到,则回滚事务。
Private Sub RestoreData(Optional ByVal worker As BackgroundWorker = Nothing)
Using conData As New MySqlConnection(strConnection.ToString)
Dim transData As MySqlTransaction
conData.Open()
transData = conData.BeginTransaction
Try
Dim intInsertCount As Integer = msbRestoreFile...<Insert>.Count
Dim intInsertsMade As Integer = 0
For Each insert In From element In msbRestoreFile...<Insert>
If Not worker Is Nothing Then
worker.ReportProgress(20 + ((70 / intInsertCount)*intInsertsMade))
intInsertsMade += 1
End If
Dim cmdData As New MySqlCommand(insert.Value, conData)
cmdData.ExecuteNonQuery()
Next
transData.Commit()
Catch exMySql As MySqlException
If Not transData Is Nothing Then
transData.Rollback()
End If
Throw exMySql
End Try
如果遇到任何错误,我将回滚所有数据插入,但让其余过程继续。这样,数据库结构将在最后就位,而不是半恢复的表加载。在我的所有应用程序中,我使用了一个相当不错的日志系统,该系统将所有针对数据库执行的 SQL 记录在一个单独的 XML 文件中,如果需要,这可以用于从程序开始使用时重建所有事务(非常困难,但可能)。如果需要恢复,我还使用此日志重建自上次备份以来(通常是过去 24 小时内)的事务。
完成恢复
一旦表被重建,约束被重新创建,数据被完美恢复,我们就继续以同样的方式恢复模式的其余部分,如函数、过程、视图等。然后就完成了,我想客户现在会对我更满意了。
使用类 - 演示应用程序
应用程序
我包含的演示应用程序是一个非常基本的应用程序,它做两件事——使用这两个类备份和恢复数据库。它由两个窗体组成:一个用于登录应用程序的窗体,以及应用程序的主窗体。登录需要用户输入服务器、用户名和密码,然后它会使用 MySqlBackUp
类的 ValidateConnection
函数进行检查。如果用户通过验证,则会打开主窗体。用户可以选择勾选一个复选框,告诉应用程序在下次启动时记住他们。这会将当前服务器名称和用户的用户名存储在应用程序设置文件中(用户在重新启动时仍需提供密码)。主窗体包含一个组合框,其中加载了用户有足够权限创建备份的数据库。它有一个带有两个选项卡的选项卡控件:一个用于备份,另一个用于恢复组合框中选定的数据库。要备份,用户只需使用组合框选择数据库,在提供的文本框中输入文件名,然后按下标有“备份”的按钮。要恢复数据库,用户只需使用组合框选择数据库,使用“浏览”按钮选择相关的备份文件,如果文件通过验证,则按下“恢复”以恢复数据库。在两种情况下,都会出现一个进度条,让用户了解事件的进度。操作完成后,会打开一个消息框,通知用户。如果发生错误,用户将收到有关问题潜在原因的通知,并获得下一步操作的建议。
主窗体加载
在 Load
事件中,我们使用 MySqlBackUp
类的 GetDataBases
方法检索服务器上的数据库名称,该方法返回一个 List(of String)
,并将 ComboBox.DataSource
设置为返回的名称列表。这确保了用户选择的数据库实际存在,并且用户能够提取备份。
错误处理
此时,详细说明我在备份类中使用的错误处理策略至关重要。在整个类中,我只使用了 Using
指令,除非我需要捕获一个非常具体的错误(即使那样,我也只是抛出错误),用于连接到数据库和写入文件等等。这确保了遇到的任何错误都会抛回给调用函数,同时仍然确保我的数据库连接等以正确的方式关闭和处置。这意味着所有潜在的错误都需要在 UI 级别处理。这意味着使用 Try
、Catch
和 End Try
块来调用我们类中的任何方法,然后通过告知用户正在发生什么并建议他们可以做些什么来重试来处理错误。这需要研究要调用的方法并找出可能发生的错误,然后处理每个潜在的错误。在我看来,除非您首先处理了所有其他潜在的异常,否则处理通用异常不是一个好的做法。
备份
当用户按下“备份”按钮时,我们首先检查以确保他们输入了有效的文件名。如果有效,我们会禁用“备份”和“退出”按钮,并显示进度条(将其设置为 0)。然后我们打开一个 FolderBrowserDialog
,以便用户可以选择保存备份文件的文件夹,然后将结果与用户输入的文件名一起使用,以获取完整的路径字符串(我为我的备份文件使用 .msb 扩展名)。然后,我们实例化一个新的 BackUp 类,使用当前用户的用户名、密码、选定的数据库和完整路径作为构造函数的参数。
Private Sub btnBackUp_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles btnBackUp.Click
If txtFileName.Text = String.Empty Then
MsgBox("Please enter valid File Name")
txtFileName.Focus()
Exit Sub
End If
btnBackUp.Enabled = False
btnExit.Enabled = False
pbBackUp.Visible = True
pbBackUp.Value = 0
Try
Dim fbdBackup As New FolderBrowserDialog
fbdBackup.ShowDialog()
BackUpFileName = fbdBackup.SelectedPath & "/" & txtFileName.Text & ".msb"
CurrentBackUp = New BackUp(CurrentServer, _
CurrentUser.Name, _
CurrentUser.Password,_
cboDataBaseSelection.SelectedItem.ToString, _
BackUpFileName)
bgwBackUp.RunWorkerAsync()
Catch exMysql As MySqlException
MsgBox("There was an error connecting with the server.etc")
Catch exXML As Xml.XmlException
MsgBox("Could not save BackUp File to Disk. Please try again.etc")
End Try
End Sub
我在设计时创建了两个 BackGroundWorker
,一个处理每个操作。这是可选的,但建议这样做,因此在创建我的 BackUp
类之后,我调用我的 Backup
BackGroundWorker
的 RunWorkerAsync
方法。Backup BackGroundWorker
的 DoWork
事件是我们调用 BackUp
类的 BackUpDataBase
例程的地方,将当前工作器作为参数传递,以便我们可以接收进度报告。在 Backup BackGroundWorker
的 ProgressChanged
事件中,我们只是将备份进度条的值设置为工作器的进度百分比。当工作器完成备份或发生错误时,它将引发其 RunWorkerCompleted
事件,我们在此处需要分析错误并向用户提供有用信息,或者让用户知道一切成功并重置 UI。
恢复
在“恢复”选项卡上,“恢复”按钮在用户选择有效的备份文件之前是禁用的。
选择备份文件
单击“浏览”按钮后,用户将看到一个 OpenFileDialog
来选择备份文件。用户选择文件后,会立即使用 MySqlBackUp
类的 ValidateBackUpFile
方法进行检查。
此方法创建一个新的 XmlDocument
类,并使用该类的 Load
方法加载选定的文档。然后我们调用 GetSchema
方法,该方法“即时”创建 XML 模式。正如我们前面所看到的,XML 文件的实际模式非常简单,因此创建其模式也很简单。
Private Function GetSchema() As XmlSchema
'Creates basic xml schema to validate selected file
Dim BackUpSchema As New Xml.Schema.XmlSchema
Dim DatabaseElement As New XmlSchemaElement
BackUpSchema.Items.Add(DatabaseElement)
DatabaseElement.Name = "DataBase"
Dim ctDataBase As New XmlSchemaComplexType()
DatabaseElement.SchemaType = ctDataBase
Dim sqDataBase As New XmlSchemaSequence
ctDataBase.Particle = sqDataBase
Dim eDataBaseName As New XmlSchemaAttribute()
ctDataBase.Attributes.Add(eDataBaseName)
eDataBaseName.Name = "Name"
eDataBaseName.Use = XmlSchemaUse.Required
'Set the DataType for the attribute
eDataBaseName.SchemaTypeName = _
New XmlQualifiedName("string", "http://www.w3.org/2001/XMLSchema")
Dim xseTables As New XmlSchemaElement()
sqDataBase.Items.Add(xseTables)
xseTables.Name = "Tables"
Dim eConstraints As New XmlSchemaElement()
sqDataBase.Items.Add(eConstraints)
eConstraints.Name = "Constraints"
Dim eInserts As New XmlSchemaElement()
sqDataBase.Items.Add(eInserts)
eInserts.Name = "Inserts"
Dim eViews As New XmlSchemaElement()
sqDataBase.Items.Add(eViews)
eViews.Name = "Views"
Dim eProcedures As New XmlSchemaElement()
sqDataBase.Items.Add(eProcedures)
eProcedures.Name = "Procedures"
Dim eFunctions As New XmlSchemaElement()
sqDataBase.Items.Add(eFunctions)
eFunctions.Name = "Functions"
Dim eEvents As New XmlSchemaElement()
sqDataBase.Items.Add(eEvents)
eEvents.Name = "Events"
Dim eTriggers As New XmlSchemaElement()
sqDataBase.Items.Add(eTriggers)
eTriggers.Name = "Triggers"
Return BackUpSchema
End Function
正如所见,这非常不言自明。我们只是将根元素数据库创建为 ComplexElement
类型,创建一个新的 XML 序列,并将数据库元素设置为该序列的基础。然后我们向数据库元素添加一个属性,以包含已备份的数据库名称。然后只是创建其他节点的简单元素的问题。
然后我们将此模式添加到 XmlDocument
的模式集合中,然后使用它来验证所选文件。为了处理遇到的任何模式错误,我们需要声明一个带有其委托的验证事件处理程序。此处理程序只是抛出错误,期望它在主 UI 中被捕获。
一旦选定的备份文件通过验证,我们将设置一个标签以显示选定的文件并启用“恢复”按钮。如果遇到任何错误,我们将使用 Try
块捕获它们,按类型捕获潜在错误并适当处理。
恢复
当备份文件验证通过后,“恢复”按钮将被启用。当用户点击该按钮时,我们首先检查(以防万一)文件是否有效(请记住,保存文件名的标签只会包含有效文件名)。我们还确保用户不是误点击。如果确定,则实例化一个新的 Restore
类,使用当前用户的详细信息、选定的数据库和选定的文件。然后检查文件数据库以确保它与选定的数据库对应,禁用“恢复”和“退出”按钮,并显示恢复进度条。然后我们调用我们的恢复 BackGroundworker
来执行其操作,在其 DoWork
例程中调用我们新的 Restore
类的 RestoreDatabase
方法。我们使用恢复工作器的 ProgressChanged
例程监视进度,当它完成或出现错误时,我们将在 RunWorkerCompleted
例程中捕获它。就这样。恢复完成 == 微笑的客户。
值得思考的要点
我几乎把我能找到的每个数据库都扔给了这个类,到目前为止,它似乎处理了我扔给它的所有东西。好吧,让我这样说吧,测试中产生的错误在测试过程中得到了解决,因此对表的每列进行数据类型分析等等。这绝不意味着它是我想要的防弹类,因为我还没有在非常大的数据库上测试过它。我想即使我有一个可以测试的,它也真的经不起考验。考虑到这一点,我尝试过的最大数据库最终生成了一个 456 MB 的 XML 文件,创建用了 2 分钟,恢复用了 5 分钟。试图在记事本中打开该文件几乎使我的计算机死机。我的客户的数据库从未接近这个大小,因此在这个阶段,超出此范围的考虑是不必要的。如果我需要一个重建更大数据库的功能,我会考虑将不同的部分分离到不同的物理文件中,特别是 INSERT
s,我可能会限制每个插入文件不超过 50,000 条记录。然后我将使用主 XML 文件作为指导,以确定哪些文件应该放在哪里,也许只是用它来按顺序列出生成的文件。
历史
- 首次发布 - 2010年6月7日。