65.9K
CodeProject 正在变化。 阅读更多。
Home

MySQL数据库备份工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (13投票s)

2010年6月7日

CPOL

23分钟阅读

viewsIcon

80787

downloadIcon

3710

一个用于生成 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. 从头开始重建数据库的最佳方法是什么?

经过研究,主要研究了其他人和其他组织是如何实现这一点的,我得出的结论是,为每个表的 CREATEINSERT 重新创建 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(我的原创性又来了)。构造函数接受四个字符串作为参数,即 UserNamePasswordDataBaseFileName,它们都相当不言自明。它使用这些参数来实例化 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 作为参数,这使得在备份过程中报告进度变得容易。基本上,这个例程所做的就是按顺序一个接一个地调用每个单独的部分,不同的步骤已经被分解成各自的例程。我不会用每个例程的每个细节来烦扰你,它们都非常相似,所以我将介绍两个最重要的例程:BackUpTablesCreateTableBackUpTables 的一个分支)和 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 (数据类型)、NullKeyDefaultExtra。收到这些信息后,只需按此顺序构建每列:名称 数据类型 (最大值) 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> 标签,如上所述。

备份约束、视图、过程和函数

上述每个部分都有自己的例程,例如,BackUpConstraintsBackUpViews 等,每个例程的性质都非常相似。基本上,它们都查询 Information_Schema 数据库以检索与当前数据库相关的实体名称,然后遍历每个找到的值并根据情况调用 SHOW CREATE FUNCTIONSHOW CREATE PROCEDURE 等。然后将该调用的结果传递给一个读取器,该读取器从正确的列中检索值并构建一个 XML 元素,然后将其添加到当前的 XDocument 中。

完成备份

一旦最初的 BackUpDataBase 例程完成了所有部分,只需调用 XDocumentSave 方法即可完成备份。

Restore 类

工作原理

Restore 类只有两个私有变量,即 ConnectionStringBuilderXDocument。该类的构造函数接受五个参数:用户名、密码、服务器、数据库和要恢复的文件的完整路径。与 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 作为参数,以方便进度报告。基本上,此例程只调用实际恢复不同相关部分的例程。它首先恢复表,然后重新创建所有外键和唯一键约束,最后恢复实际数据。通过这种方式,我认为数据在恢复时将针对所有约束进行验证。从那时起,它会重新创建所有函数、过程等(如果存在)。由于所有不同的部分都非常相似,我将只介绍 RestoreTablesRestoreData 例程。

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 例程(RestoreConstraintsRestoreViews 等)遵循的基本过程,除了 RestoreData 例程,我使用 MySqlTransaction 来执行 Inserts,因为这样,如果出现问题,总是有回滚的选项,而且速度也快得多。在所有其他情况下使用事务是适得其反的,因为在执行 CREATE TABLECREATE FUNCTION 等语句时,MySQL 会隐式提交,因此事务将是多余的。

RestoreData

我让这个例程接受一个可选的 BackGroundWorker 作为参数,以便我们可以在进行过程中报告进度。除了计算我们在过程中走了多远的一些代码之外,其他一切都与之前发生的事情非常相似,只是我们使用 TryCatchEnd 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 级别处理。这意味着使用 TryCatchEnd 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 BackGroundWorkerRunWorkerAsync 方法。Backup BackGroundWorkerDoWork 事件是我们调用 BackUp 类的 BackUpDataBase 例程的地方,将当前工作器作为参数传递,以便我们可以接收进度报告。在 Backup BackGroundWorkerProgressChanged 事件中,我们只是将备份进度条的值设置为工作器的进度百分比。当工作器完成备份或发生错误时,它将引发其 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 分钟。试图在记事本中打开该文件几乎使我的计算机死机。我的客户的数据库从未接近这个大小,因此在这个阶段,超出此范围的考虑是不必要的。如果我需要一个重建更大数据库的功能,我会考虑将不同的部分分离到不同的物理文件中,特别是 INSERTs,我可能会限制每个插入文件不超过 50,000 条记录。然后我将使用主 XML 文件作为指导,以确定哪些文件应该放在哪里,也许只是用它来按顺序列出生成的文件。

历史

  • 首次发布 - 2010年6月7日。
© . All rights reserved.