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

一款 21 KB 的数据层,满足您的所有数据传输需求

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.50/5 (5投票s)

2010年12月31日

CPOL

7分钟阅读

viewsIcon

24325

downloadIcon

130

如何构建一个单一的数据层 DLL,可供任何应用程序使用。

引言

在本文中,我将向您展示如何构建一个能够满足所有数据传输需求的数据访问层,包括多连接,而且仅占 21 KB。

概述

下载文件中的代码包含一个包含两个项目的解决方案。本文将介绍的类是:

  • Datalayer - 编译为 21 KB DLL 的项目
    • TableRelationItem - 用于存储两个表之间关系信息的类
    • TableRelationList - 用于存储关系集合的类
    • ADatalayer - 处理数据库连接和数据传输的类
  • DatalayerUI - 展示如何使用 Datalayer.dll 的项目
    • config.xml - 包含连接字符串的文件
    • DatalayerRT - 继承自 ADatalayer 的类
    • Form1 - 用于说明实例化 datalayer 的类

Datalayer 项目中的类取自我在我的书《Visual Basic 设计模式实战(第二版)》中介绍的 BaseClasses 解决方案中的三个项目。

代码

Datalayer.TableRelationItem

此类的目的是存储两个表的关联信息。ADatalayer 中一个返回 DataSet 的方法会使用此类的实例。为简便起见,下面的代码仅显示类级别变量声明和一个验证方法。下载中提供的完整代码包含每个变量的 Property 方法。

Public Class TableRelationItem

    Protected mRelationName As String = ""
    Protected mPrimaryTable As String = ""
    Protected mPrimaryField As String = ""
    Protected mForeignTable As String = ""
    Protected mForeignField As String = ""

    Public Function IsValid() As Boolean

        Try

            If mRelationName = "" Then Return False
            If mPrimaryTable = "" Then Return False
            If mPrimaryField = "" Then Return False
            If mForeignTable = "" Then Return False
            If mForeignField = "" Then Return False

            Return True

        Catch ex As Exception

            Throw

        Finally


        End Try

    End Function

End Class  

Datalayer.TableRelationList

此类的目的是提供 TableRelationItem 的集合,ADatalayer 中一个返回 DataSet 的方法会使用它。我倾向于在需要将它绑定到可见控件时使用 BindingList。在这种情况下,List(Of ) 同样有效。

请注意,Add() 方法会检查关系是否有效。

Imports System.ComponentModel

Public Class TableRelationList
    Inherits BindingList(Of TableRelationItem)

    Public Sub New()
        MyBase.AllowNew = True
    End Sub

    Public Shadows Function Add(ByVal pTableRelation As TableRelationItem) As Boolean

        Try

            If pTableRelation.IsValid Then
                MyBase.Items.Add(pTableRelation)
            Else
                Throw New Exception("Invalid table relation")
            End If

            Return True

        Catch ex As Exception

            Throw

        Finally


        End Try

    End Function

End Class

Datalayer.ADatalayer

这是最复杂的类。我将只讨论选定的方法,以便您了解其范围和能力。此类的声明为 abstract,因为它封装了数据传输所需的所有工作,但本身不包含实际的连接字符串。使用此类时,应用程序会有一个继承自 ADatalayer 的类,并提供该应用程序所需的连接字符串。

我将为这个类及其子类使用单例设计模式。每个人只需要一个数据访问层。实现单例设计模式需要满足以下条件:

  1. abstract 类中有一个类级别变量,用于返回它本身的实例
  2. abstract 类中,New() 方法声明为 Protected,以防止外部类实例化它
  3. 在运行时子类中有一个共享方法,用于返回它本身的实例。我使用一个名为 GetInstance() 的方法

类签名和类级别变量如下所示。它继承自 IDatalayer,该接口未在本文中介绍,但存在于下载文件中。类级别变量 mDatalayer 用于满足上述第一个条件。

Public MustInherit Class ADatalayer
    Implements IDatalayer

    Protected Shared mDataLayer As ADatalayer = Nothing
    Protected Shared mConnections As PropertyCollection = Nothing

End Class        

New() 方法声明为 Protected,以便子类可以访问但外部类无法访问。这满足了上述第二个条件。

此方法接受连接字符串列表,打开所有连接并将它们添加到 PropertyCollection。我使用了 DataTable 作为一种便利,因为 XML 文件可以轻松导入到 DataSet 中,并且连接表会从那里传入。

我见过许多代码“迟开早关”,以最小化连接保持打开的时间,使其可供他人使用。然而,我发现关闭连接并不总是能立即将其释放给下一个用户。“正在使用”的连接开始堆积。此外,打开连接是过程中成本最高的部分。为什么要关闭它?为每个用户在应用程序生命周期内打开一个连接,并在 Finalize() 方法中关闭它。

Protected Sub New(ByVal pConnectionStrings As DataTable)
    Dim row As DataRow
    Dim con As SqlClient.SqlConnection

    Try

        mConnections = New PropertyCollection

        For Each row In pConnectionStrings.Rows
            con = New SqlClient.SqlConnection(row("Value"))
            con.Open()
            mConnections.Add(row("Name"), con)
        Next

    Catch ex As Exception

        Throw

    Finally


    End Try

End Sub

GetConnection() 方法接受所需连接的名称,并从 PropertyCollection 中返回该连接。

Protected Function GetConnection(ByVal ConnectionName As String) _
	As SqlClient.SqlConnection Implements IDatalayer.GetConnection

    Try

        Return mConnections(ConnectionName)

    Catch ex As Exception

        Throw

    Finally


    End Try

End Function

GetCommand() 方法使用提供的参数准备并返回一个 SqlCommand 对象。

  • pConnectionName - 要使用的连接的名称
  • pComandText - 可以是 SQL 语句或存储过程的名称
  • pCommandType - 指定命令的类型
  • pParameters - 要由参数化 SQL 语句或存储过程使用的名称/值对集合
  • pOutputValues - 指定为输出参数的参数名称的可选列表

一旦 SqlCommand 对象被实例化,该方法就会遍历 pParameters PropertyCollection 中的每个项并创建一个 SqlParameter。如果参数名称在 pOutputValues 列表中,则相应地设置 SqlParameter 的方向。

Private Function GetCommand( _
    ByVal pConnectionName As String, _
    ByVal pComandText As String, _
    ByVal pCommandType As System.Data.CommandType, _
    ByRef pParameters As PropertyCollection, _
    Optional ByVal pOutputValues As List(Of String) = Nothing _
) As SqlClient.SqlCommand Implements IDatalayer.GetCommand

    Dim cmd As SqlClient.SqlCommand
    Dim prm As SqlClient.SqlParameter

    Try

        cmd = New SqlClient.SqlCommand
        cmd.Connection = GetConnection(pConnectionName)
        cmd.CommandTimeout = 300
        cmd.CommandType = pCommandType
        cmd.CommandText = pComandText

        If Not pParameters Is Nothing Then
            For Each prmKey In pParameters.Keys
                prm = New SqlClient.SqlParameter()
                prm.ParameterName = prmKey
                prm.Value = pParameters(prmKey)
                If Not pOutputValues Is Nothing Then
                    If pOutputValues.Contains(prmKey) Then
                        prm.Direction = ParameterDirection.InputOutput
                    End If
                End If
                cmd.Parameters.Add(prm)
            Next
        End If

        Return cmd

    Catch ex As Exception

        Throw

    Finally

    End Try

End Function

有两个 ExecuteSelect() 方法。第一个返回一个 DataTable,相对直接。由于它返回一个 DataTable,因此不需要 pOutputValues 列表。此方法使用的参数与上面描述的相同。

Public Function ExecuteSelect( _
    ByVal pConnectionName As String, _
    ByVal pComandText As String, _
    ByVal pCommandType As System.Data.CommandType, _
    ByVal pProperties As PropertyCollection _
) As DataTable Implements IDatalayer.ExecuteSelect

    Dim cmd As SqlClient.SqlCommand
    Dim adp As SqlClient.SqlDataAdapter
    Dim tbl As DataTable

    Try

        cmd = GetCommand(pConnectionName, pComandText, pCommandType, pProperties, Nothing)
        adp = New SqlClient.SqlDataAdapter(cmd)
        tbl = New DataTable
        adp.Fill(tbl)

        Return tbl

    Catch ex As Exception

        Throw

    Finally

        cmd = Nothing
        adp = Nothing
        tbl = Nothing

    End Try

End Function        

第二个 ExecuteSelect() 方法返回一个 DataSet,因此它有两个额外的参数:

  • pTableNames - 返回的表的名称列表
  • pRelations - 一个 TableRelationList,包含 TableRelationItem 对象的集合

此方法的前几行与第一个 ExecuteSelect() 方法相似,不同之处在于填充的是 DataSet 而不是 DataTablepComandText 参数应包含多个 SQL Select 语句或调用返回多个记录集的存储过程。接下来,检查 pTableNames 参数并设置 DataSetDataTable 的名称。然后检查 pRelations 并将 DataRelations 添加到 DataSet。最后,返回 DataSet

Public Function ExecuteSelect( _
    ByVal pConnectionName As String, _
    ByVal pComandText As String, _
    ByVal pCommandType As System.Data.CommandType, _
    ByVal pProperties As PropertyCollection, _
    ByVal pTableNames As List(Of String), _
    ByVal pRelations As TableRelationList _
) As DataSet Implements IDatalayer.ExecuteSelect

    Dim cmd As SqlClient.SqlCommand
    Dim adp As SqlClient.SqlDataAdapter
    Dim dst As DataSet
    Dim ndx As Integer
    Dim pTableRelation As TableRelationItem
    Dim dataRel As DataRelation
    Dim dcPrimary(0) As DataColumn
    Dim dcForeign(0) As DataColumn

    Try

        cmd = GetCommand(pConnectionName, pComandText, pCommandType, pProperties, Nothing)
        adp = New SqlClient.SqlDataAdapter(cmd)
        dst = New DataSet
        adp.Fill(dst)

        If Not pTableNames Is Nothing Then
            For ndx = 0 To pTableNames.Count - 1
                dst.Tables(ndx).TableName = pTableNames(ndx)
            Next
        End If

        If Not pRelations Is Nothing Then
            For Each pTableRelation In pRelations
                dcPrimary(0) = dst.Tables(pTableRelation.PrimaryTable)._
				Columns(pTableRelation.PrimaryField)
                dcForeign(0) = dst.Tables(pTableRelation.ForeignTable)._
				Columns(pTableRelation.ForeignField)
                dataRel = New DataRelation(pTableRelation.RelationName, _
				dcForeign, dcPrimary, False)
                dataRel.Nested = True
                dst.Relations.Add(dataRel)
            Next
        End If

        Return dst

    Catch ex As Exception

        Throw

    Finally

        cmd = Nothing
        adp = Nothing
        dst = Nothing
        pTableRelation = Nothing
        dataRel = Nothing
        dcPrimary = Nothing
        dcForeign = Nothing

    End Try

End Function        

ExecuteNonQuery() 展示了如何使用输出参数。pProperties 参数以 ByRef 传递,以便调用者可以检索通过 UpdateParameters() 方法更新的值。

Public Function ExecuteNonQuery( _
    ByVal pConnectionName As String, _
    ByVal pComandText As String, _
    ByVal pCommandType As System.Data.CommandType, _
    ByRef pProperties As PropertyCollection, _
    ByVal pOutputValues As List(Of String) _
) As Integer Implements IDatalayer.ExecuteNonQuery

    Dim cmd As SqlClient.SqlCommand
    Dim rtn As Integer

    Try

        cmd = GetCommand(pConnectionName, pComandText, _
		pCommandType, pProperties, pOutputValues)
        rtn = cmd.ExecuteNonQuery

        UpdateParameters(pProperties, pOutputValues, cmd.Parameters)

        Return rtn

    Catch ex As Exception

        Throw

    Finally

        cmd = Nothing

    End Try

End Function        

UpdateParameters() 方法根据 pOutputValues List 更新 pProperties PropertyCollection

Private Sub UpdateParameters( _
    ByRef pProperties As PropertyCollection, _
    ByVal pOutputValues As List(Of String), _
    ByVal pParameters As SqlClient.SqlParameterCollection _
) Implements IDatalayer.UpdateParameters

    Dim prmKey As String

    Try

        If Not pProperties Is Nothing AndAlso Not pOutputValues Is Nothing Then
            For Each prmKey In pOutputValues
                pProperties.Remove(prmKey)
                pProperties.Add(prmKey, pParameters(prmKey).Value)
            Next
        End If

    Catch ex As Exception

        Throw

    Finally


    End Try

End Sub

添加了一个 DefaultConnectionName() 方法供运行时子类使用。稍后将对此进行解释。

MustOverride Function DefaultConnectionName( _
) As String Implements IDatalayer.DefaultConnectionName        

ADatalayer 中包含以下方法,但本文未予讨论:

  • ExecuteReader()
  • ExecuteScalar()
  • ExecuteXmlReader()

这些方法非常直接,不需要解释。

config.xml

我使用 XML 配置文件而不是 app.config 文件来简化配置信息的检索。app.config 文件用于提供应用程序环境如此广泛的配置信息,以至于难以提取用户所需的仅有信息。该文件包含连接字符串,并根据解决方案配置放置在相应的 bin 目录中。

<?xml version="1.0" encoding="utf-8"?>
<Configuration>
  <Connection Name="Development" Value="Data Source=DevDB;
	Initial Catalog=ContactList;Integrated Security=True" />
  <Connection Name="Production" Value="Data Source=ProdDB;
	Initial Catalog=ContactList;Integrated Security=True" />
</Configuration>        

DatalayerUI.DatalayerRT

此类的目的是为特定应用程序提供一个运行时数据访问层。下面显示了类签名和 New() 方法:

Public Class DatalayerRT
    Inherits DataLayer.ADatalayer

    Protected Sub New(ByVal pConnectionStrings As DataTable)
        MyBase.New(pConnectionStrings)
    End Sub

End Class        

由于此类继承自 ADatalayer,因此必须重写 DefaultConnectionName() 方法。我使用条件编译来确定基于当前解决方案配置要连接的数据库。我之所以这样做,是因为我大多数时候要么连接到开发数据库,要么连接到生产数据库。应用程序很少连接到多个数据库,但数据访问层的架构允许这样做。

    Public Overrides Function DefaultConnectionName() As String

        Try

#If DEBUG Then
            Return "Development"
#Else
            Return "Production"
#End If

        Catch ex As Exception

            Throw

        Finally


        End Try

    End Function        

最后,为了满足实现单例设计模式的第三个条件,我提供了一个共享方法来返回类的实例:

Public Shared Function GetInstance( _
    ByVal pConnectionStrings As DataTable _
) As DataLayer.IDatalayer

    Try

        If mDataLayer Is Nothing Then
            mDataLayer = New DatalayerRT(pConnectionStrings)
        End If

        Return mDataLayer

    Catch ex As Exception

        Throw

    Finally


    End Try

End Function  

Form1

此窗体的目的是展示如何使用数据访问层。下面的代码显示了 Form1 类中的 New() 方法,以及我将使用的变量。我将单独介绍实际的示例。

Public Class Form1

    Protected mDataLayer As DatalayerRT

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Dim ds As DataSet = Nothing
        Dim pc As PropertyCollection = Nothing
        Dim dt As DataTable = Nothing
        Dim count As Int32 = 0
        Dim tri As Datalayer.TableRelationItem = Nothing
        Dim trl As Datalayer.TableRelationList = Nothing
        Dim lst As List(Of String) = Nothing

        Try

        Catch ex As Exception

            Throw

        Finally

            lst = Nothing
            trl = Nothing
            tri = Nothing
            dt = Nothing
            pc = Nothing
            ds = Nothing

        End Try

    End Sub

End Class        

示例 1:实例化 DatalayerRT

将以下代码添加到 Try 子句中:

ds = New DataSet
ds.ReadXml("config.xml")

mDataLayer = DatalayerRT.GetInstance(ds.Tables("Connection"))     

此代码将 config.xml 文件加载到 DataSet 中,并将“Connection”表传递给 DatalayerRTGetInstance() 方法。

示例 2:使用带参数的 SQL 语句执行 ExecuteScalar()

pc = New PropertyCollection
pc.Add("EmailAddressTypeID", 1)

count = mDataLayer.ExecuteScalar( _
   mDataLayer.DefaultConnectionName, _
   "SELECT COUNT(*) FROM EmailAddress WHERE (EmailAddressTypeID = @EmailAddressTypeID)", _
   CommandType.Text, _
   pc, Nothing)
        

示例 3:使用 ExecuteSelect() 从存储过程返回 DataTable

dt = mDataLayer.ExecuteSelect( _
    mDataLayer.DefaultConnectionName, _
    "ReadEmailAddress", _
    CommandType.StoredProcedure, _
    pc)        

示例 4:使用 ExecuteSelect() 从两个参数化 SQL SELECT 语句返回 DataSet

这需要四个步骤:

  1. 定义表关系
  2. 定义表名
  3. 设置参数
  4. 发起调用
'Step 1: Define the table relationships
'EmailAddress table contains a foreign key PersonID in table Person
tri = New Datalayer.TableRelationItem
tri.RelationName = "PersonEmailAddress"
tri.PrimaryTable = "EmailAddress"
tri.PrimaryField = "PersonID"
tri.ForeignTable = "Person"
tri.ForeignField = "PersonID"

trl = New Datalayer.TableRelationList
trl.Add(tri)

'Step 2: Define the table names
lst = New List(Of String)
lst.Add("Person")
lst.Add("EmailAddress")

'Step 3: Set the parameters
pc.Clear()
pc.Add("PersonID", 1)

'Step 4: Make the call
ds = mDataLayer.ExecuteSelect( _
    mDataLayer.DefaultConnectionName, _
    "SELECT * FROM Person WHERE (PersonID = @PersonID);" _
    & "SELECT * FROM EmailAddress WHERE (PersonID = @PersonID)", _
    CommandType.StoredProcedure, _
    pc, lst, trl)        

摘要

有可能创建一个 Datalayer DLL,它可以满足所有数据传输需求,并被任何需要它的应用程序使用。该 DLL 在我的机器上编译为 21 KB,我只需将其添加为应用程序的引用并创建运行时 DatalayerRT 类即可使用它。

历史

  • 2010 年 12 月 31 日:首次发布
© . All rights reserved.