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

Visual Basic 使用反射映射 MySQL 数据库中的 DataTable

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2013 年 8 月 19 日

CPOL

14分钟阅读

viewsIcon

31625

downloadIcon

354

轻松映射 MySQL 数据库表的方法。

目录

  • 引言和背景
  • 它是如何工作的?
    • 第一部分:反射基础知识
      1. 创建自定义属性
      2. 使用反射和自定义属性进行动态编码
        1. 使用 GetType 关键字进行反射
        2. 获取映射 Schema 中的属性
        3. 值分配
      3. 创建映射表的 Schema 类
      4. 测试
    • 第二部分:MySQL 数据库映射工作
      1. 创建自定义属性
      2. 创建一个辅助类
      3. 创建存储 Schema 信息的表类
      4. 数据库映射工作
        1. 类组件
        2. 映射过程
      5. 测试
  • 关注点
  • 代码改进
    1. 更通用的编码方式
    2. 使其适用于多种用户场景
    3. MySQL 中的字段属性组合
  • 附加

引言和背景

对我个人而言,读取数据库中的数据记录是一件非常痛苦的事情,因为有时你可能需要修改数据库中的表 Schema。每次修改数据库中的数据表结构时,你都必须修改数据获取函数的处理代码。我们能否在 Visual Basic 中让这项工作变得简单?也许我们可以使用一种动态代码,它利用反射来映射你的数据表 Schema,从而使你的数据库编码工作更加轻松。

它是如何工作的?

第一部分:反射基础知识

1. 创建自定义属性

创建自定义属性是一项非常简单的工作。你只需要创建一个继承自 [Attribute] 类的类类型。完成类的属性和构造函数后,你就可以创建一个自定义属性类。以下是我们必须在数据库映射工作中使用的自定义属性类:

''' <summary> 
''' Custom attribute class to mapping the field in the data table.
''' (用于映射数据库中的表中的某一个字段的自定义属性类型)
''' </summary>
''' <remarks></remarks>
<AttributeUsage(AttributeTargets.Property, allowmultiple:=False, inherited:=True)>
Public Class DatabaseField : Inherits Attribute
 
    ''' <summary>
    ''' Get or set the name of the database field.
    ''' (获取或者设置数据库表中的字段的名称)
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns> 
    ''' <remarks></remarks>
    Public Property FieldName As String
 
    ''' <summary>
    ''' Construct a new database field attribute object.
    ''' (构造出一个新的数据库字段属性对象) 
    ''' </summary>
    ''' <param name="DbFiledName">Database field name.(数据库字段名称)</param>
    ''' <remarks></remarks>
    Sub New(DbFiledName As String)
        FieldName = DbFiledName
    End Sub
End Class     

2. 使用反射和自定义属性进行动态编码

反射是 .NET 编程中的强大工具。使用反射可以让你创建动态代码来满足处理未知对象类型的需求。但正如中国有句老话:“鱼和熊掌不可兼得也”,使用反射可以使我们的编码工作更加轻松,但作为使用反射的结果,我们可能会损失程序很多性能。

要使用反射,我们应该遵循以下步骤:

a. 使用 GetType 关键字进行反射

使用 [GetType] 关键字进行反射操作非常简单。首先,我们应该创建一个 Function 对象,用于使用反射映射数据库表。此函数如下所示:

''' <summary>
''' Query a data table using Reflection.(使用反射机制来查询一个数据表)
''' </summary>
''' <typeparam name="ItemType">
''' Mapping schema to our data table.(对我们的数据表的映射类型)
''' </typeparam>
''' <param name="SQL">Sql 'SELECT' query statement.(Sql 'SELECT' 查询语句)</param>
''' <returns>The target data table.(目标数据表)</returns>
''' <remarks></remarks>
Public Function Query(Of ItemType)(SQL As String) As List(Of ItemType)   

[ItemType] 类型参数是我们用于映射数据库的 Schema 类。[SQL] 很容易理解,它是一个 SELECT SQL 查询语句。然后,我们使用反射关键字:[GetType] 来获取我们映射 Schema 类的类型信息。

Dim Type As Type = GetType(ItemType)  

现在我们已经获得了映射 Schema 类的类型信息。在下一步中,我们将获取此映射 Schema 中的所有属性(在数据库中称为 Field)。

b. 获取映射 Schema 中的属性

在此步骤中,我们定义一个变量来存储 Schema 类中的属性信息,该变量如下所示:

Dim ItemTypeProperty = Type.GetProperties   

这只是一个简单的任务,不是吗?你还可以使用 [Type] 对象中的不同方法来获取存储在此类中的方法和事件信息,例如:Type.GetMethodsType.GetEvents 等等。事实上,你不需要在定义语句中定义变量的类型,因为 Visual Basic 编译器会在你编译程序时自动完成这项工作,如果你定义了一个变量并同时为其分配了特定类型的值。(你不能一开始就将变量定义为 Object 类型,因为此操作会在 IL 反汇编代码中产生大量装箱操作。)

c. 值分配

在为我们的 Schema 类分配值之前,我们应该创建一个映射 Schema 类型对象实例。这是一个动态过程,使用了反射:

'Create a instance of specific type: our record schema. 
Dim FillObject = Activator.CreateInstance(Type)  

由于我们在上一步中已经获得了属性信息,我们可以利用这些信息来动态工作。现在,我们需要一个 For 循环来扫描 Schema 类中的所有属性:

For i As Integer = 0 To ItemTypeProperty.Length - 1
[Property] = ItemTypeProperty(i)    

然后,我们从元数据:[DatabaseField] 中获取自定义属性,并从中读取我们之前创建的自定义属性类。

Attributes = [Property].GetCustomAttributes(GetType(DatabaseField), True) 

接下来,我们通过检查该属性是否具有自定义属性元数据来判断此属性:是否是我们的映射字段。

If Not Attributes Is Nothing AndAlso Attributes.Length = 1 Then 

因此,这个属性是我们映射数据库表中特定字段的属性,然后我们将从数据库读取的值分配给我们的映射属性。

CustomAttr = CType(Attributes(0), DatabaseField)

If Not CustomAttr Is Nothing AndAlso CustomAttr.FieldName.Length > 0 Then
   Ordinal = Reader.GetOrdinal(CustomAttr.FieldName)
   If Ordinal >= 0 Then              
       [Property].SetValue(FillObject, Reader.GetValue(Ordinal), Nothing)
   End If
End If

现在我们已经成功设置了映射类对象中其中一个属性的值,然后我们继续处理下一个属性来分配值。这是一个简单的任务:Next 关键字语句。

在这里,我将整个动态查询函数的代码发布如下:

''' <summary>
''' Query a data table using Reflection.(使用反射机制来查询一个数据表)
''' 
''' <typeparam name="ItemType">
''' Mapping schema to our data table.(对我们的数据表的映射类型)
''' 
''' <param name="SQL" />Sql 'SELECT' query statement.(Sql 'SELECT' 查询语句)
''' <returns>The target data table.(目标数据表)
''' <remarks>
Public Function Query(Of ItemType)(SQL As String) As List(Of ItemType)
    Dim Type As Type = GetType(ItemType)
   
    '[ConnectionString] is a compiled mysql connection string from our class constructor. 
    Dim MySql As MySqlConnection = New MySqlConnection(ConnectionString)
    Dim MySqlCommand As MySqlCommand = New MySqlCommand(SQL, MySql)
    Dim Reader As Global.MySql.Data.MySqlClient.MySqlDataReader = Nothing
    Dim NewList As New List(Of ItemType)
    Dim CustomAttr As DatabaseField
    Dim Ordinal As Integer = 0
    Dim Attributes As Object()
    Dim ItemTypeProperty = Type.GetProperties
    Dim [Property] As PropertyInfo
    Try
        MySql.Open()
        Reader = MySqlCommand.ExecuteReader(CommandBehavior.CloseConnection)
        While Reader.Read
        'When we call this function, the pointer will 
        'move to next line in the table automatically.   
          
            'Create a instance of specific type: our record schema. 
            Dim FillObject = Activator.CreateInstance(Type) 
            For i As Integer = 0 To ItemTypeProperty.Length - 1
                [Property] = ItemTypeProperty(i)
                Attributes = [Property].GetCustomAttributes(GetType(DatabaseField), True)
                If Not Attributes Is Nothing AndAlso Attributes.Length = 1 Then
                    CustomAttr = CType(Attributes(0), DatabaseField)
                    If Not CustomAttr Is Nothing AndAlso CustomAttr.FieldName.Length > 0 Then
                        Ordinal = Reader.GetOrdinal(CustomAttr.FieldName)
                        If Ordinal >= 0 Then
                            [Property].SetValue(FillObject, Reader.GetValue(Ordinal), Nothing)
                        End If
                    End If
                End If
            Next
            NewList.Add(FillObject)
        End While
        Return NewList 'Return the new table that we get
    Catch ex As Exception
    Finally
        If Not Reader Is Nothing Then Reader.Close()
        If Not MySqlCommand Is Nothing Then MySqlCommand.Dispose()
        If Not MySql Is Nothing Then MySql.Dispose()
    End Try
    Return Nothing
End Function

3. 创建映射表的 Schema 类

创建我们用于映射表的 Schema 类,只需使用我们创建的自定义属性即可!

Public Class TestRecord
   
    <DatabaseField("RegistryNumber")> Public Property ID As ULong
    <DatabaseField("GUID")> Public Property GUID As String
    <DatabaseField("DataModel")> Public Property Model As String
   
    Public Overrides Function ToString() As String
        Return String.Format("{0}, {1}, {2}", ID, GUID, Model)
    End Function
  
    Shared Narrowing Operator CType(e As TestRecord) As String
        Return String.Format("{0}, {1}, {2}", e.ID, e.GUID, e.Model)
    End Operator
End Class   

4. 测试

现在我们可以创建一个 MySQL 数据库服务器来测试此代码:

Dim MYSQL As Oracle.LinuxCompatibility.MySQL.Client.DbReflector =
    <MYSQL>https://:1002/client?user=lab613%password=1234%database=gcmodeller</MYSQL>

Dim Stopwatch As New Stopwatch
Dim Table As List(Of TestRecord)

Call Stopwatch.Start()
Table = MYSQL.Query(Of TestRecord)("SELECT * FROM test;")
Call Stopwatch.Stop()
Call Console.WriteLine("Total time cost by query and reflection operation: {0} ms", Stopwatch.ElapsedMilliseconds)

For Each Record In Table
    Console.WriteLine(Record)
Next
Console.Read()          

好的,开始了。点击 [开始] 运行我们的测试程序。片刻之后,我们从 MySQL 数据库中的一个数据表中获取了所有数据记录。但是正如你所见,与普通编码相比,由反射创建的动态代码太慢了。我希望你能解决这个性能问题。

测试输出

Total time cost by query and reflection operation: 116 ms
1, {CED4E2AF-E63D-4E5E-A365-210740EB2964}, i have no idea
2, {3A43A713-5A0A-463D-95D0-618461140E11}, xie.guigang@gmail.com
3, {A6715C38-B559-4462-891D-4C700EC0D342}, 1234567890
4, {369C6AB8-1111-4578-8B12-53C6F8E7EE39}, 9876543210
5, {8EA2183B-416B-48BD-A837-B05A11448EFA}, abcd
6, {6D4C8D12-B6A1-4C6F-9EB7-622E68216035}, 1234
7, {5F633B6A-8111-4AE4-83A1-9F4BBA1F387F}, google
8, {E49DAAE6-314B-4649-8809-21FAC0457B13}, baidu
9, {C68D1744-6D60-44EE-BC03-65E0CE17CF85}, none sense
10, {A063370C-CA5F-43FB-B7C3-F45CFA6657BD}, yaaaaah 

第二部分:MySQL 数据库映射工作

尽管反射工作缓慢,但反射使用的自定义属性使类对象的定义更加有趣。看起来我们可以直接从类定义创建表,这是一个更复杂的示例类定义:

<TableName("Test")>
Public Class TestRecord
    <DataType(MySqlDbType.BigInt)> <AutoIncrement> <NotNULL> _
         <Unsigned> <Unique> <PrimaryKey> <DatabaseField("RegistryNumber")>
    Public Property ID As ULong
    <NotNULL> <DataType(MySqlDbType.VarChar, "45")> _
      <Unique> <DatabaseField("GUID")> Public Property GUID As String
    <DataType(MySqlDbType.LongText)> <NotNULL> <DatabaseField("DataModel")>
    Public Property Model As String
    ''' <summary>
    ''' This property is not a field in the table because
    ''' it has no custom attribute of type DatabaseField
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property ImNotAField As String
    Public Overrides Function ToString() As String
        Return String.Format("{0}, {1}, {2}", ID, GUID, Model)
    End Function
    Shared Narrowing Operator CType(e As TestRecord) As String
        Return String.Format("{0}, {1}, {2}", e.ID, e.GUID, e.Model)
    End Operator
End Class 

如果我们想创建一个新表,就不需要编写任何 'CREATE TABLE' SQL 文本,因为这段 SQL 文本是通过使用我们在类对象上定义的自定义属性的反射自动创建的。这个功能看起来很棒,它是如何工作的?为了介绍这个功能是如何工作的,让我们看看如何使用它。这是一个简单的任务:

Dim Schema As Oracle.LinuxCompatibility.MySQL.Client.Reflection.Schema.Table = GetType(TestRecord)
Dim SQL As String = Oracle.LinuxCompatibility.MySQL.Client.Reflection.SQL.CreateTableSQL.FromSchema(Schema)
Console.WriteLine(SQL)  

首先,我们使用 Table 对象获取类自定义属性上定义的表 Schema,然后使用从先前获得的表 Schema 的 CreateTableSQL 辅助对象中的共享函数生成 CREATE TABLE SQL 文本。然后,我们使用此 SQL 文本在数据库中创建一个表。这是一项愉快的任务,无需编写更多代码。但是,为了实现此功能,这并非易事。所以,让我们看看它是如何工作的:

1. 创建自定义属性

为了实现这种方式,我们需要更多的自定义属性,这是一个简单的任务。请在我的上传的解决方案文件中使用 **Object Browser** 查看 Oracle.LinuxCompatibility.MySQL.Client.Reflection.DbAttributes 命名空间。我们需要在此处定义自定义属性。请注意,为了使 Visual Studio 中的 **Class Diagram** 更加友好,我让所有其他自定义属性继承自 DbAttribute 类,因此,除了组织类之间的关系外,DbAttribute 类没有其他功能,无所谓。因此,正如你所看到的,此命名空间中定义的某些类就像 DbAttribute 一样是空的,因为 MySQL 中的某些字段属性只是 **Boolean** 类型值,所以不需要更多信息来编写 UniquePrimaryKeyNotNULLBinaryUnsignedZeroFillAutoIncrement 的类。只有一个标志属性表示字段属性具有此 MySQL 字段属性。TableName 属性类的用法与其他属性不同,它只作用于类定义。这种差异来自于这个类定义的属性:<AttributeUsage(AttributeTargets.Class, allowmultiple:=False, inherited:=True)>,而其他属性类声明为 AttributeTargets.Property。因此,此数据库字段属性仅作用于类定义,而其他属性类仅作用于属性定义。自定义属性的用法定义可以是 **Method、Field(类成员)、Module 等**,这取决于你想让代码在哪种问题上工作。

2. 创建一些辅助类

为了使工作更轻松并匹配 Visual Basic 语言的 OO(面向对象)特性,我创建了另一个类对象来存储此命名空间中定义的字段属性。这是 Field 类定义的一部分:

Public Class Field
    Public FieldName As String
    Public Unique As Boolean
    Public PrimaryKey As Boolean
    Public DataType As _
        Oracle.LinuxCompatibility.MySQL.Client.Reflection.DbAttributes.MySqlDbType
    
    Public Unsigned As Boolean
    Public NotNull As Boolean
    Public AutoIncrement As Boolean
    Public ZeroFill As Boolean
    Public Binary As Boolean
    Public [Default] As String = String.Empty
    ''' <summary>
    ''' The property information of this custom database field attribute. 
    ''' </summary>
    ''' <remarks></remarks>
    Public [PropertyInfo] As PropertyInfo   

这个类包含了我认为我能找到的所有 MySQL 数据库的字段属性。正如我所指出的,有些自定义属性类是空的,仅用作布尔标志,并在该类中相应的成员处赋值。

为了使解析工作更轻松、更干净,我将自定义属性解析过程重建为一个新函数,该函数写法与我上面发布的先前函数(名称:Function Query(Of ItemType)(SQL As String) As List(Of ItemType))类似。这是我重建的新函数:

''' <summary>
''' Get the specific type of custom attribute from a property.
''' (从一个属性对象中获取特定的自定义属性对象)
''' </summary> 
''' <typeparam name="T">The type of the custom attribute.(自定义属性的类型)</typeparam>
''' <param name="Property">Target property object.(目标属性对象)</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetAttribute(Of T As Attribute)([Property] As PropertyInfo) As T
    Dim Attributes As Object() = [Property].GetCustomAttributes(GetType(T), True)
    If Not Attributes Is Nothing AndAlso Attributes.Length = 1 Then
        Dim CustomAttr As T = CType(Attributes(0), T)
        If Not CustomAttr Is Nothing Then
            Return CustomAttr
        End If
    End If
    Return Nothing
End Function 

你可以发现,在类构造函数(名称:Shared Widening Operator CType([Property] As PropertyInfo) As Field)中会出现很多值分配语句,例如:

Field.Unique = Not GetAttribute(Of Unique)([Property]) Is Nothing 

是的,许多字段属性只是充当 **Boolean** 标志。

3. 创建存储 Schema 信息的表类

CREATE TABLE SQL 命令文本的分析中,我们可以发现创建表需要一些基本属性:TableName、Fields 定义。因此,Table 类包含两个成员(TableNameFields)。其他成员不是必需的,但从你那里要求它们来使 SQL 更有效、执行更快,例如索引等等。我在此处发布了 Table 类中定义的所有成员:

''' <summary>
''' The table schema that we define on the custom attributes of a Class.
''' </summary>
''' <remarks></remarks>
Public Class Table
    Public Property TableName As String
    Public UniqueFields As New List(Of String)
    Public PrimaryField As New List(Of String)
    Public Fields As New List(Of Field)
    ''' <summary>
    ''' The index field when execute the update/delete sql.
    ''' </summary>
    ''' <remarks>
    ''' Long/Integer first, then the Text is second, the primary key is the last consideration.
    ''' </remarks>
    Friend Index As String, IndexProperty As PropertyInfo
    Public SchemaType As System.Type

在该类的构造函数中,我们使用 GetSchema 方法来获取创建表所需的必要信息:

Private Sub GetSchema(Schema As Type)
    Dim ItemProperty = Schema.GetProperties
    Dim Field As Field
    Dim Index2 As String = String.Empty
    Dim IndexProperty2 As PropertyInfo = Nothing
    
    TableName = GetTableName(Schema)
    For i As Integer = 0 To ItemProperty.Length - 1
        'Parse the field attribute from the ctype operator, this property must have a 
        DatabaseField custom attribute to indicate that it is a database field. 
        Field = ItemProperty(i) 
        If Not Field Is Nothing Then
            Call Fields.Add(Field)
            If Field.PrimaryKey Then
                PrimaryField.Add(Field.FieldName)
            End If
            If Field.Unique Then
                UniqueFields.Add(Field.FieldName)
                If Commonly.Numerics.IndexOf(Field.DataType) > -1 AndAlso Field.PrimaryKey Then
                    Index = Field.FieldName
                    IndexProperty = ItemProperty(i)
                End If
                Index2 = Field.FieldName
                IndexProperty2 = ItemProperty(i)
            End If
        End If
    Next 
    Call Indexing(Index2, IndexProperty2, ItemProperty)
    'If we can not found a index from its unique field, then we indexing from its primary key.
End Sub   

该方法与我在上一节介绍的 Query(Of T) 函数类似:使用 GetType 对特定类型进行反射,然后使用 Type.GetProperties 获取其所有属性信息,接着解析属性自定义属性以获取我们所需的信息,最后,我们获得特定类型对象上定义的表 Schema 信息。

最后,通过以上工作,我们获得了创建表的所有必要信息。现在我们可以开始生成 CREATE TABLE SQL,使用我们拥有的表 Schema。这是 SQL 生成函数,这是一项简单的任务:这个生成任务只是一个字符串格式化任务:

''' <summary>
''' Generate the 'CREATE TABLE' sql command.
''' (生成'CREATE TABLE' sql命令)
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function FromSchema(Schema As Table) As String
    Dim sBuilder As StringBuilder = New StringBuilder(1024)
    Dim sBuilder2 As StringBuilder = New StringBuilder(128)
    sBuilder.AppendFormat(CREATE_TABLE & vbCrLf, Schema.TableName)
    
    Dim Fields = Schema.Fields
    For i As Integer = 0 To Fields.Count - 1
        sBuilder.AppendLine("  " & Fields(i).ToString & " ,")
    Next
    Dim PrimaryField = Schema.PrimaryField
    For Each PK As String In PrimaryField
        sBuilder2.AppendFormat("`{0}`, ", PK)
    Next
    sBuilder2.Remove(sBuilder2.Length - 2, 2)
    sBuilder.AppendFormat(PRIMARY_KEY & vbCrLf, sBuilder2.ToString)
    Dim UniqueFields = Schema.UniqueFields
    If UniqueFields.Count > 0 Then
        sBuilder.Append(" ,")
    End If
    For Each UniqueField As String In UniqueFields
        sBuilder.AppendLine(UNIQUE_INDEX.Replace("%s", UniqueField) & " ,")
    Next
    sBuilder.Remove(sBuilder.Length - 3, 3)
    sBuilder.Append(");") 'End of the sql statement
    Return sBuilder.ToString
End Function  

我还创建了一个 SQL 文本生成器辅助类,以使编码工作更轻松。你可以使用 Visual Studio 中的 **Object Browser** 在 Oracle.LinuxCompatibility.MySQL.Client.Reflection.SQL 命名空间中找到辅助类。该辅助类包括 **INSERT、UPDATE、DELETE** 命令,并且该辅助类功能齐全,其工作方式类似于 **CREATE TABLE** 辅助类。这个命名空间只包含这些命令,但足以满足我们的数据库操作编码需求。 

4. 数据库映射工作

a. 类组件

现在我们有了一个辅助类和函数来映射 MySQL 数据库中的表,我们可以开始我们的映射工作了。在我看来,映射工作应该包含表创建工作(DDL)和数据操作工作(DML),因此映射类包含四个 SQL 辅助类(CREATE、INSERT、UPDATE、DELETE),我们之前已经创建了它们(CREATE 辅助类显示为共享方法,因此它不是类成员)。

''' <summary>
''' 'DELETE' sql text generator of a record that type of schema.
''' </summary>
''' <remarks></remarks>
Dim DeleteSQL As SQL.Delete(Of Schema)
''' <summary>
''' 'INSERT' sql text generator of a record that type of schema.
''' </summary>
''' <remarks></remarks>
Dim InsertSQL As SQL.Insert(Of Schema)
''' <summary>
''' 'UPDATE' sql text generator of a record that type of schema.
''' </summary>
''' <remarks></remarks>
Dim UpdateSQL As SQL.Update(Of Schema) 

并且这个类应该有能力与 MySQL 数据库服务器交换数据,所以我们有一个 MySQL 客户端封装类,它提供了我们所需的功能。

Dim WithEvents MySQL As MySQL 

然后我们需要一个对象来将数据存储到数据库中,所以我们有一个列表对象:

''' <summary>
''' DataSet of the table in the database.
''' (数据库的表之中的数据集)
''' </summary>
''' <remarks></remarks>
Friend _ListData As New List(Of Schema)(capacity:=1024)
''' <summary>
''' DataSet of the table in the database. Do not edit the data directly from this property...
''' (数据库的表之中的数据集,请不要直接在这个属性之上修改数据)
''' </summary>
''' <remarks></remarks>
ReadOnly Property ListData As List(Of Schema)
    Get
        Return _ListData
    End Get
End Property

一个属性用于输出从 MySQL 服务器返回的错误消息。

''' <summary>
''' The error information that come from MYSQL database server.
''' (来自于MYSQL数据库服务器的错误信息)
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ErrorMessage As String  

哦,我们还需要一个 SQL 事务命令文本存储对象,以减少服务器的 CPU 和网络流量使用。

''' <summary>
''' The sql transaction that will be commit to the mysql database.
''' (将要被提交至MYSQL数据库之中的SQL事务集)
''' </summary>
''' <remarks></remarks>
Dim Transaction As StringBuilder = New StringBuilder(2048)

所以现在我们拥有了所有必需的东西,可以开始工作了吗?是的,我们几乎完成了,但我们没有一个类构造函数来获取数据库服务器的连接信息。所以我们在此类中添加这个构造函数:

''' <summary>
''' Initialize the mapping from a connection object
''' </summary>
''' <param name="e"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Widening Operator CType(e As ConnectionHelper) As DataTable(Of Schema)
    Return New DataTable(Of Schema) With {.MySQL = e}
End Operator
''' <summary>
''' Initialize the mapping from a connection string
''' </summary>
''' <param name="e"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Widening Operator CType(e As String) As DataTable(Of Schema)
    Return New DataTable(Of Schema) With {.MySQL = e}
End Operator
''' <summary>
''' Initialize the mapping from a connection string
''' </summary>
''' <param name="e"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Widening Operator CType(e As Xml.Linq.XElement) As DataTable(Of Schema)
    Return New DataTable(Of Schema) With {.MySQL = CType(e, ConnectionHelper)}
End Operator 
b. 映射过程

首先,我们在类的构造函数方法中获取 Schema 信息,这项工作看起来很神奇,一项非常简单的任务,并将每个 SQL 生成助手复制一份表 Schema,以便助手类能够正确地操作特定类类型 Schema 的对象实例:

Friend Sub New()
    'Start reflection and parsing the table structure information.
    TableSchema = GetType(Schema)
    'Initialize the sql generator using the table structure
    'information that parse from the custom attribut of a class object.
    DeleteSQL = TableSchema  
    InsertSQL = TableSchema
    UpdateSQL = TableSchema
End Sub   

然后,为了在数据库中获取数据,我们使用了上面第一部分编写的 Query(Of T) 函数的改进版本。它不再是简单的表 Schema 解析工作,而是直接使用存储在模块变量中的表 Schema。这只是一个从用户对象分配的查询获取的数据集的数据获取工作。

''' <summary>
''' Load the data from database server. Please notice that: every time you 
''' call this function, the transaction will be commit to the database server in.
''' (从数据库服务器之中加载数据,请注意:每一次加载数据都会将先前的所积累下来的事务提交至数据库服务器之上)
''' </summary>
''' <param name="Count">
''' The count of the record that will be read from the server. Notice: Zero or negative is stands for 
''' load all records in the database.
''' (从数据库中读取的记录数目。请注意:值0和负数值都表示加载数据库的表中的所有数据)
''' </param>
''' <remarks></remarks>
Public Sub Fetch(Optional Count As Integer = 100)
    Call Me.Commit()  '
    If Count <= 0 Then  'Load all data when count is zero or negative.
        _ListData = Query(String.Format("SELECT * FROM {0};", TableSchema.TableName))
        p = _ListData.Count
    Else
        Dim NewData As List(Of Schema)
        NewData = Query(String.Format("SELECT * FROM {0} LIMIT {1},{2};", _
                         TableSchema.TableName, p, Count))
        _ListData.AddRange(NewData)
        p += Count  'pointer move next block.
    End If
End Sub  

为了减少数据集的数据流量,此函数有两种查询数据模式:查询所有行或查询有限数量的记录。查询的 Select SQL 非常简单:“select * from <table>;” 并使用 limit 关键字在 MySQL 中限制数量,语法如下:

Select * from <table> limit <start Line>,<record counts>; 

接下来是 DML 操作:删除、更新、插入行。这三个函数具有相似的流程:使用辅助类生成 SQL 文本,然后将 SQL 存储到事务中,然后对 ListData 成员执行特定操作。

为了在我们的输入记录对象的特定属性中获取数据,我们使用了反射,这非常简单:

Dim value As String = Field.PropertyInfo.GetValue(Record, Nothing).ToString 

然后我们创建一个值列表,在 String.Format 使用这个值列表后,最终我们得到一个针对特定记录对象实例的特定操作的 SQL 命令。

最后一件事情是通过 Commit 方法提交事务到数据库,以使数据库更改永久生效。

''' <summary>
''' Commit the transaction to the database server to make the change permanently.
''' (将事务集提交至数据库服务器之上以永久的修改数据库) 
''' </summary>
''' <returns>The transaction commit is successfully or not.(事务集是否被成功提交)</returns>
''' <remarks></remarks>
Public Function Commit() As Boolean
    If Transaction.Length = 0 Then Return True 'No transaction will be commit to database server.
    If MySQL.CommitTransaction(Transaction.ToString) Then
        Call Transaction.Clear()  'Clean the previous transaction when the transaction commit is successfully. 
        Return True
    Else 'the transaction commit failure.
        ErrorMessage = MySQL.ErrMsg
        Return False
    End If
End Function 

5. 测试

我所有的测试代码都可以在 libarytest 项目中找到。

Sub DataTableTest()
    Dim Table As Oracle.LinuxCompatibility.MySQL.Client.Reflection.DataTable(Of TestRecord) = 
        <url>https://:1002/client?user=lab613%password=1234%database=gcmodeller</url>
    Call Table.Create()
    Table.Fetch()  'No data was fetched
    Dim Record = New TestRecord With {.GUID = "{1AEBD086-50F7-43E8-A6DC-8F4A9EA430ED}", .ID = 1, .Model = "1234567890"}
    Table.Insert(Record)
    Record = New TestRecord With {.GUID = "{3A43A713-5A0A-463D-95D0-618461140E11}", .ID = 2, .Model = "xie.guigang@gmail.com"}
    Table.Insert(Record)
    Record = Table.ListData.First
    Record.Model = "I have no idea!"
    Table.Update(Record)
    Table.Delete(Table.ListData.First)
    If Table.Commit() = False Then
        Console.WriteLine(Table.ErrorMessage)
    Else
        Console.WriteLine("Transaction commit to database successfully!")  'One record in the database
    End If
End Sub   

控制台输出

CREATE  TABLE `Test` (
<pre>  `RegistryNumber` BigInt UNSIGNED NOT NULL AUTO_INCREMENT  ,
  `GUID` VarChar (45) NOT NULL  ,
  `DataModel` LongText NOT NULL  ,
PRIMARY KEY (`RegistryNumber`)
 ,UNIQUE INDEX `RegistryNumber_UNIQUE` (`RegistryNumber` ASC) ,
UNIQUE INDEX `GUID_UNIQUE` (`GUID` ASC) );
INSERT INTO `Test` (`RegistryNumber`, `GUID`, `DataModel`) VALUES ('1', '{1AEBD0
86-50F7-43E8-A6DC-8F4A9EA430ED}', '1234567890');
INSERT INTO `Test` (`RegistryNumber`, `GUID`, `DataModel`) VALUES ('2', '{3A43A7
13-5A0A-463D-95D0-618461140E11}', 'xie.guigang@gmail.com');
UPDATE `Test` SET `RegistryNumber`='1', `GUID`='{1AEBD086-50F7-43E8-A6DC-8F4A9EA
430ED}', `DataModel`='I have no idea!' WHERE `RegistryNumber`='1';
DELETE FROM `Test` WHERE `RegistryNumber`='1';
Transaction commit to database successfully!
Total time cost by query and reflection operation: 108 ms
2, {3A43A713-5A0A-463D-95D0-618461140E11}, xie.guigang@gmail.com   

关注点

正如你在我的编码工作中看到的,出现了很多 CType 运算符重载函数。在我看来,CType 运算符是一个内联函数,所以这个属性将使你的类型转换工作更快,你的代码更简洁,因为我们重载了值分配运算符“=”。因此,从这个角度来看,我们可以知道在 Visual Basic 中,值分配操作有两种方式:值复制和类型转换。要重载运算符 =,只需重载 Visual Basic 中的布尔判断运算符。如果我们想重载值分配运算符,我们应该重载运算符 CType。这是一个正常的方法,而不是装箱操作,因为我们在其 **MSIL** 反汇编代码中找不到任何装箱运算符,所以这个重载不会降低代码的效率,反而使代码更简洁。

代码改进

1. 更通用的编码

SQL 命令文本语法和数据类型在不同 DBMS 之间略有不同,此模块中定义的语法仅适用于 MySQL DBMS。而且我相信,通过一些修改,这个模块可以在其他 DBMS 上正常工作。但它仍然不够智能,我正在努力使其无需任何代码修改就能在所有已知 DBMS 上正常工作。

2. 使其适用于多种用户场景

正如你所看到的,Delete、Insert、Update 函数只在内存中对表进行更改,而修改不会立即发生在服务器上。更改将在提交事务到服务器时进行。因此,如果多个用户使用此模块修改同一个表,一些更改将丢失。这种情况非常糟糕。因此,这个模块在多用户场景下工作得不太好,我正在努力解决这个问题。

3. MySQL 中的字段属性组合

某些数据类型和字段属性在 MySQL 中不允许组合使用,例如“Text Unsigned”字段属性组合是非法的。这种组合关系相当复杂,我正在努力解决这个组合问题。因此,在问题解决之前,当你使用此模块创建 Schema 时,请阅读 MySQL 语法手册,以确保你的 Schema 中的字段属性组合是正确的。

附加

如果你想使用 Visual Basic .NET 连接到 MySQL 数据库服务器,你应该先安装 MySQL .NET Connector。你可以从 MySQL 官方网站下载连接器库安装程序(MSI 包)(使用 apt-get 命令在 Ubuntu 上安装:sudo apt-get install libmysql6.4-cil 或搜索“MySQL database connector for CLI”并在 Ubuntu 软件中心安装)。该库程序集是使用 Microsoft Visual Studio 2013 Preview、MySQL 5.6 开发的,并在 Windows 7(.NET Framework 4.5)/Ubuntu 13.04(Novell mono 2.1)上成功调试。

Visual Basic 使用反射映射 MySQL 数据库中的 DataTable - CodeProject - 代码之家
© . All rights reserved.