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






3.50/5 (5投票s)
如何构建一个单一的数据层 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
的类,并提供该应用程序所需的连接字符串。
我将为这个类及其子类使用单例设计模式。每个人只需要一个数据访问层。实现单例设计模式需要满足以下条件:
- 在
abstract
类中有一个类级别变量,用于返回它本身的实例 - 在
abstract
类中,New()
方法声明为Protected
,以防止外部类实例化它 - 在运行时子类中有一个共享方法,用于返回它本身的实例。我使用一个名为
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
而不是 DataTable
。pComandText
参数应包含多个 SQL Select
语句或调用返回多个记录集的存储过程。接下来,检查 pTableNames
参数并设置 DataSet
中 DataTable
的名称。然后检查 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
”表传递给 DatalayerRT
的 GetInstance()
方法。
示例 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
这需要四个步骤:
- 定义表关系
- 定义表名
- 设置参数
- 发起调用
'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 日:首次发布