承繼之前的系統分層概念,在資料存取層中,應該只有資料的存取,沒有商業的邏輯。小喵之前的系統並沒有這一層,以往都是商業邏輯混搭資料存取。這一篇就來嘗試撰寫資料的存取層。順便開始程式撰寫部分的介紹。
緣起:
承繼之前的系統分層概念(請參考:淺談多層式架構 (Multi Tiers)),在資料存取層中,應該只有資料的存取,沒有商業的邏輯。小喵之前的系統並沒有這一層,以往都是商業邏輯混搭資料存取。這一篇就來嘗試撰寫資料的存取層。順便開始程式撰寫部分的介紹。
方案管理:
為了方便未來程式撰寫不用開好幾個VS,我們透過方案的方式可以將資料層、商業邏輯層、介面層(Web Project)集中在一個方案中。所以目前先介紹怎麼使用方案。
先來看圖說故事:首先要建立一個空的方案
接著在空的方案上新增一個專案
此時在方案總館中,小喵只看到專案而沒有看到方案,要加入第二個專案時,需要從功能表中的檔案來做
加好第二個專案後,在方案總館裡面就會看到如下圖這樣
我們在方案中再增加Web站台,最後就會成這樣
未來維護時,就可以直接開啟方案就能夠在此方案中維護各層的程式。
資料存取層設計:
接著開始來設計資料存取層。資料存取層應該只負責資料的存取,不包含任何的商業邏輯。因此小喵回顧一下以前撰寫的程式中,有關資料存取的部分有哪些
- 讀取:(不支援Transaction)
- 傳回整批資料:傳回DataTable,DataSet。
(這裡不用DataReader,由於分散式系統,如果傳回的是連線型的DataReader,那麼關閉Connection的時機不易掌握,而且很可能一不小心就忘了關閉連線。在撰寫ADO.NET的時候,應該要讓Connection用完儘快的釋放) - 檢查資料是否存在:傳回True/False
(檢查資料是否存在,這裡可以用DataReader來檢查。)
- 傳回整批資料:傳回DataTable,DataSet。
- 維護:(支援Transaction)
- 傳入SQL語法,進行維護資料,傳回維護的筆數(如果需要的話)。
撰寫COM+元件的方式、步驟,請大家參考一下小喵以前的文章【N-Tiers開發方式(如何使用VB.NET撰寫COM+元件)】
連接字串的處理方式
連接當有資料存取層後,連接字串只會存在資料存取層中,在商業邏輯層與展現層將不會用到連接字串。而未了修改方便,小喵會將連接字串使用文字檔的方式將設定檔放在實體硬碟中的某個位置(例如:C:\MyConnStr),並且設定其附檔名為ini,此位置可以授權給某特別的帳號可以存取,然後將此帳號設定為元件的識別帳號。這樣元件就可以透過COM+切換身分的方式,透過此帳號取得連接字串。而這個資料夾沒有網路分享、沒有Web分享,一般的帳號都無法存取,甚至實體切層時,他與商業邏輯層、展現層不同的主機。用這樣的方式來提高連接字串的安全性,也方便未來如果更改設定時,可以直接在Server上變更,不用修改程式。
相關的程式如下:
- Imports System.EnterpriseServices
- Imports System.Runtime.InteropServices
- Imports System.IO
- <Guid("1317D493-AD28-4912-9958-34098EC3A988"), _
- EventTrackingEnabled(True)> _
- Public Class CDMTConn
- Inherits ServicedComponent
- Public Function GetConnStr(ByVal DBName As String) As String
- '*************************************************************************
- '** 撰寫者:topcat(topcat) 撰寫日期:2006/2/8
- '** 用途: 1.讀取Connection String的方式
- '** 做法:
- '** 1.傳入參數DataBase Name DBName
- '** 2.透過Stream將文字檔讀出
- '** 3.將取得的資料傳回
- '** 4.關閉相關物件
- '** 注意事項:
- '** 1.
- '** 2.
- '** 維護記錄:
- '** 維護者:姓名(員工代號) 維護日期:日期
- '** 維護項目:
- '** 1.
- '** 2.
- '** 做法: 1.
- '** 2.
- '** 注意事項:
- '** 1.
- '*************************************************************************
- Dim StrmRd As New StreamReader("C:\DataLink\" + DBName + ".ini")
- Dim Line As String = ""
- Dim ConnStr As String = ""
- Try
- Do
- Line = StrmRd.ReadLine()
- If Line <> "" Then
- ConnStr += Line
- End If
- Loop Until Line Is Nothing
- GetConnStr = ConnStr
- Catch ex As Exception
- Throw New Exception(ex.Message.ToString)
- Finally
- StrmRd.Close()
- StrmRd.Dispose()
- StrmRd = Nothing
- End Try
- End Function
- End Class
參數Parameter的處理:
為了防止SQL Injection,透過Parameter的方式已經變成常識了。所以本來小喵預計寫的時候,直接傳入sqlParameter的Collection物件集合,就可以運用了,不過事與願違,測試過程中小喵一直遇到Proxy通道未開啟無法傳遞的問題。COM+的方式可以用物件(Object)來當做Function的參數傳遞,但是前提是這個物件要用COM+的方式定義撰寫。因此小喵再撰寫一個類別,用來作為傳遞Parameter參數的媒介。這是初體驗下撰寫的程式碼,還不算完整,相關程式碼如下
- Imports System.EnterpriseServices
- Imports System.Runtime.InteropServices
- Imports System.Data.SqlClient
- <Guid("438F9A2B-9CF2-4D88-95AC-868CC2D22EA8"), _
- EventTrackingEnabled(True)> _
- Public Class CDMTParameters
- Inherits ServicedComponent
- Private m_PName As String
- Private m_PTypeName As String
- Private m_PValue As String
- Public Property PName() As String
- Get
- Return m_PName
- End Get
- Set(ByVal value As String)
- m_PName = value
- End Set
- End Property
- Public Property PTypeName() As String
- Get
- Return m_PTypeName
- End Get
- Set(ByVal value As String)
- m_PTypeName = value
- End Set
- End Property
- Public Property PValue() As String
- Get
- Return m_PValue
- End Get
- Set(ByVal value As String)
- m_PValue = value
- End Set
- End Property
- Public ReadOnly Property sParameter() As SqlParameter
- Get
- Dim tPr As New SqlParameter
- If m_PName <> "" Then
- tPr.ParameterName = m_PName
- 'tPr.TypeName = m_PTypeName
- Select Case m_PTypeName.ToString.ToLower
- Case "string"
- tPr.Value = CType(m_PValue, String)
- Case "integer"
- tPr.Value = CType(m_PValue, Integer)
- Case "decimal"
- tPr.Value = CType(m_PValue, Decimal)
- End Select
- End If
- Return tPr
- End Get
- End Property
- End Class
讀取的元件內容:
準備工作大致告一段落,接著終於要來撰寫資料存取的元件。首先是讀取的部分,相關程式碼如下:
- Imports System.EnterpriseServices
- Imports System.Runtime.InteropServices
- Imports System.Data.SqlClient
- Imports System.Data
- <Guid("75A6EE3A-C86F-4C48-A39C-026B9D7B4141"), _
- EventTrackingEnabled(True)> _
- Public Class CDMTDT00
- Inherits ServicedComponent
- '無Transaction的資料存取元件
- Const DBName As String = "DMT"
- Public Function GetDataTable(ByVal SqlTxt As String, ByVal Parameters As List(Of CDMTParameters)) As DataTable
- '有Parameter的讀取資料並透過DataTable傳回一批資料。
- Try
- Dim Dt As New DataTable
- Dim oConnStr As New CDMTConn
- Dim ConnStr As String = oConnStr.GetConnStr(DBName)
- Using Conn As New SqlConnection(ConnStr)
- Using Cmmd As New SqlCommand(SqlTxt, Conn)
- If Parameters.Count > 0 Then
- Dim y As Integer
- For y = 0 To Parameters.Count - 1
- Cmmd.Parameters.Add(Parameters.Item(y).sParameter)
- Next
- End If
- Dim Da As New SqlDataAdapter(Cmmd)
- Da.Fill(Dt)
- End Using
- End Using
- Return Dt
- Catch ex As Exception
- Throw New Exception(ex.Message)
- End Try
- End Function
- Public Function GetDataTable(ByVal SqlTxt As String) As DataTable
- '無Parameter的讀取資料並透過DataTable傳回一批資料
- Try
- Dim Dt As New DataTable
- Dim oConnStr As New CDMTConn
- Dim ConnStr As String = oConnStr.GetConnStr(DBName)
- Using Conn As New SqlConnection(ConnStr)
- Using Cmmd As New SqlCommand(SqlTxt, Conn)
- Dim Da As New SqlDataAdapter(Cmmd)
- Da.Fill(Dt)
- End Using
- End Using
- Return Dt
- Catch ex As Exception
- Throw New Exception(ex.Message)
- End Try
- End Function
- Public Function ChkDataExist(ByVal SqlTxt As String, ByVal Parameters As List(Of CDMTParameters)) As Boolean
- '檢查資料是否存在
- Try
- Dim Rc As Boolean = False
- Dim oConnStr As New CDMTConn
- Dim ConnStr As String = oConnStr.GetConnStr(DBName)
- Using Conn As New SqlConnection(ConnStr)
- Conn.Open()
- Using Cmmd As New SqlCommand(SqlTxt, Conn)
- If Parameters.Count > 0 Then
- Dim y As Integer
- For y = 0 To Parameters.Count - 1
- Cmmd.Parameters.Add(Parameters.Item(y).sParameter)
- Next
- End If
- Dim Dr As SqlDataReader = Cmmd.ExecuteReader
- If Dr.HasRows Then
- Rc = True
- Else
- Rc = False
- End If
- Dr.Close()
- End Using
- End Using
- Return Rc
- Catch ex As Exception
- Throw New Exception(ex.Message)
- End Try
- End Function
- End Class
以上這些只是初步的測試,小喵心想,應該還要個傳入SQL語法並傳回一個DataSet的Function。不過這個部分就帶下回有機會再分享。
維護的元件內容:
- Imports System.EnterpriseServices
- Imports System.Runtime.InteropServices
- Imports System.Data.SqlClient
- Imports System.Data
- <Guid("9007AB8A-CB61-43E8-8405-062695E0FFE5") _
- , Transaction(TransactionOption.Required) _
- , Synchronization(SynchronizationOption.Required) _
- , JustInTimeActivation(True) _
- , EventTrackingEnabled(True)> _
- Public Class CDMTDT01
- Inherits ServicedComponent
- <AutoComplete()> _
- Public Function ExecChange(ByVal SqlTxt As String, ByVal Parameters As List(Of CDMTParameters)) As String
- Try
- Dim oConnStr As New CDMTConn
- Dim ConnStr As String = oConnStr.GetConnStr("DMT")
- Using Conn As New SqlConnection(ConnStr)
- Conn.Open()
- Using Cmmd As New SqlCommand(SqlTxt, Conn)
- If Parameters.Count > 0 Then
- Dim y As Integer
- For y = 0 To Parameters.Count - 1
- Cmmd.Parameters.Add(Parameters.Item(y).sParameter)
- Next
- End If
- Cmmd.ExecuteNonQuery()
- End Using
- End Using
- Return "Success"
- Catch ex As Exception
- Throw New Exception(ex.Message)
- End Try
- End Function
- <AutoComplete()> _
- Public Function ExecChange(ByVal SqlTxt As String, ByVal Parameters As List(Of CDMTParameters), ByRef ChgCnt As Integer) As String
- Try
- Dim oConnStr As New CDMTConn
- Dim ConnStr As String = oConnStr.GetConnStr("DMT")
- Using Conn As New SqlConnection(ConnStr)
- Conn.Open()
- Using Cmmd As New SqlCommand(SqlTxt, Conn)
- If Parameters.Count > 0 Then
- Dim y As Integer
- For y = 0 To Parameters.Count - 1
- Cmmd.Parameters.Add(Parameters.Item(y).sParameter)
- Next
- End If
- ChgCnt = Cmmd.ExecuteNonQuery()
- End Using
- End Using
- Return "Success"
- Catch ex As Exception
- Throw New Exception(ex.Message)
- End Try
- End Function
- End Class
以上程式撰寫完成後,當然還須將元件註冊到元件服務中,註冊的部份請參考以前小喵所撰寫的這篇【N-Tiers開發方式(COM+元件的註冊、修改)】
末記:這樣真是資料存取層嗎??
其實這樣的方式,小喵個人覺得並非完整的資料存取層。小喵個人覺得資料存取層的特性有以下:
- 資料存取層不包含任何的商業邏輯(這點可以符合)
- 可以抽出成為實體不同主機的分層(這點也可以)
- 資料庫更換時,只需修改資料存取層,不需修改商業邏輯層或者介面層的程式(並不符合)
小喵的方式,第三個並不符合。因為所有的SQL語法還是從商業邏輯層組好後,往這裡丟過來運作,然而不同的資料庫,盡管都是用SQL語法,但是語法上或多或少還是有差異,因此更換資料庫的時候,還是必須修改商業邏輯層裡面的SQL語法。小喵曾想過,或許可以直接把LINQ用LINQ包裝後,讓商業邏輯層透過LINQ的方式存取資料庫。不過小喵測試撰寫維護時還是會有些問題。似乎沒有辦法將LINQ在COM+多層架構運用的很好。這點小喵還要進一步研究。
寫到這邊到此稍作擱筆,下面還要介紹商業邏輯、展現層的相關程式,再來看怎麼實際運作成果。
以下是簽名:
- 歡迎轉貼本站的文章,不過請在貼文主旨上加上【轉貼】,並在文章中附上本篇的超連結與站名【topcat姍舞之間的極度凝聚】,感恩大家的配合。
- 小喵大部分的文章會以小喵熟悉的語言VB.NET撰寫,如果您需要C#的Code,也許您可以試著用線上的工具進行轉換,這裡提供幾個參考
Microsoft MVP Visual Studio and Development Technologies (2005~2019/6) | topcat Blog:http://www.dotblogs.com.tw/topcat |