[ASP.NET]COM+多層式程式撰寫(資料存取層初體驗)

承繼之前的系統分層概念,在資料存取層中,應該只有資料的存取,沒有商業的邏輯。小喵之前的系統並沒有這一層,以往都是商業邏輯混搭資料存取。這一篇就來嘗試撰寫資料的存取層。順便開始程式撰寫部分的介紹。

緣起:

承繼之前的系統分層概念(請參考:淺談多層式架構 (Multi Tiers)),在資料存取層中,應該只有資料的存取,沒有商業的邏輯。小喵之前的系統並沒有這一層,以往都是商業邏輯混搭資料存取。這一篇就來嘗試撰寫資料的存取層。順便開始程式撰寫部分的介紹。

方案管理:

為了方便未來程式撰寫不用開好幾個VS,我們透過方案的方式可以將資料層、商業邏輯層、介面層(Web Project)集中在一個方案中。所以目前先介紹怎麼使用方案。

先來看圖說故事:首先要建立一個空的方案

MT0001 MT0002

 

接著在空的方案上新增一個專案

MT0003

此時在方案總館中,小喵只看到專案而沒有看到方案,要加入第二個專案時,需要從功能表中的檔案來做

MT0004

加好第二個專案後,在方案總館裡面就會看到如下圖這樣

MT0005

我們在方案中再增加Web站台,最後就會成這樣

MT0006

未來維護時,就可以直接開啟方案就能夠在此方案中維護各層的程式。

資料存取層設計:

接著開始來設計資料存取層。資料存取層應該只負責資料的存取,不包含任何的商業邏輯。因此小喵回顧一下以前撰寫的程式中,有關資料存取的部分有哪些

  • 讀取:(不支援Transaction)
    • 傳回整批資料:傳回DataTable,DataSet。
      (這裡不用DataReader,由於分散式系統,如果傳回的是連線型的DataReader,那麼關閉Connection的時機不易掌握,而且很可能一不小心就忘了關閉連線。在撰寫ADO.NET的時候,應該要讓Connection用完儘快的釋放)
    • 檢查資料是否存在:傳回True/False
      (檢查資料是否存在,這裡可以用DataReader來檢查。)
  • 維護:(支援Transaction)
    • 傳入SQL語法,進行維護資料,傳回維護的筆數(如果需要的話)。

撰寫COM+元件的方式、步驟,請大家參考一下小喵以前的文章【N-Tiers開發方式(如何使用VB.NET撰寫COM+元件)

連接字串的處理方式

連接當有資料存取層後,連接字串只會存在資料存取層中,在商業邏輯層與展現層將不會用到連接字串。而未了修改方便,小喵會將連接字串使用文字檔的方式將設定檔放在實體硬碟中的某個位置(例如:C:\MyConnStr),並且設定其附檔名為ini,此位置可以授權給某特別的帳號可以存取,然後將此帳號設定為元件的識別帳號。這樣元件就可以透過COM+切換身分的方式,透過此帳號取得連接字串。而這個資料夾沒有網路分享、沒有Web分享,一般的帳號都無法存取,甚至實體切層時,他與商業邏輯層、展現層不同的主機。用這樣的方式來提高連接字串的安全性,也方便未來如果更改設定時,可以直接在Server上變更,不用修改程式。

相關的程式如下:

  1. Imports System.EnterpriseServices  
  2. Imports System.Runtime.InteropServices  
  3. Imports System.IO  
  4.  
  5. <Guid("1317D493-AD28-4912-9958-34098EC3A988"), _  
  6. EventTrackingEnabled(True)> _  
  7. Public Class CDMTConn  
  8.     Inherits ServicedComponent  
  9.  
  10.     Public Function GetConnStr(ByVal DBName As String) As String 
  11.         '*************************************************************************  
  12.         '**     撰寫者:topcat(topcat)     撰寫日期:2006/2/8  
  13.         '**     用途:  1.讀取Connection String的方式  
  14.         '**     做法:  
  15.         '**             1.傳入參數DataBase Name DBName  
  16.         '**             2.透過Stream將文字檔讀出  
  17.         '**             3.將取得的資料傳回  
  18.         '**             4.關閉相關物件  
  19.         '**     注意事項:  
  20.         '**             1.  
  21.         '**             2.  
  22.         '**     維護記錄:  
  23.         '**         維護者:姓名(員工代號)     維護日期:日期  
  24.         '**         維護項目:  
  25.         '**                 1.  
  26.         '**                 2.  
  27.         '**         做法:  1.  
  28.         '**                 2.  
  29.         '**         注意事項:  
  30.         '**                 1.  
  31.         '*************************************************************************  
  32.  
  33.         Dim StrmRd As New StreamReader("C:\DataLink\" + DBName + ".ini")  
  34.         Dim Line As String = "" 
  35.         Dim ConnStr As String = "" 
  36.         Try 
  37.             Do 
  38.                 Line = StrmRd.ReadLine()  
  39.                 If Line <> "" Then 
  40.                     ConnStr += Line  
  41.                 End If 
  42.             Loop Until Line Is Nothing 
  43.             GetConnStr = ConnStr  
  44.  
  45.         Catch ex As Exception  
  46.             Throw New Exception(ex.Message.ToString)  
  47.  
  48.         Finally 
  49.             StrmRd.Close()  
  50.             StrmRd.Dispose()  
  51.             StrmRd = Nothing 
  52.  
  53.         End Try 
  54.     End Function 
  55. End Class 

參數Parameter的處理:

為了防止SQL Injection,透過Parameter的方式已經變成常識了。所以本來小喵預計寫的時候,直接傳入sqlParameter的Collection物件集合,就可以運用了,不過事與願違,測試過程中小喵一直遇到Proxy通道未開啟無法傳遞的問題。COM+的方式可以用物件(Object)來當做Function的參數傳遞,但是前提是這個物件要用COM+的方式定義撰寫。因此小喵再撰寫一個類別,用來作為傳遞Parameter參數的媒介。這是初體驗下撰寫的程式碼,還不算完整,相關程式碼如下

  1. Imports System.EnterpriseServices  
  2. Imports System.Runtime.InteropServices  
  3. Imports System.Data.SqlClient  
  4.  
  5. <Guid("438F9A2B-9CF2-4D88-95AC-868CC2D22EA8"), _  
  6. EventTrackingEnabled(True)> _  
  7. Public Class CDMTParameters  
  8.     Inherits ServicedComponent  
  9.  
  10.     Private m_PName As String 
  11.     Private m_PTypeName As String 
  12.     Private m_PValue As String 
  13.  
  14.     Public Property PName() As String 
  15.         Get 
  16.             Return m_PName  
  17.         End Get 
  18.         Set(ByVal value As String)  
  19.             m_PName = value  
  20.         End Set 
  21.     End Property 
  22.  
  23.     Public Property PTypeName() As String 
  24.         Get 
  25.             Return m_PTypeName  
  26.         End Get 
  27.         Set(ByVal value As String)  
  28.             m_PTypeName = value  
  29.         End Set 
  30.     End Property 
  31.  
  32.     Public Property PValue() As String 
  33.         Get 
  34.             Return m_PValue  
  35.         End Get 
  36.         Set(ByVal value As String)  
  37.             m_PValue = value  
  38.         End Set 
  39.     End Property 
  40.  
  41.     Public ReadOnly Property sParameter() As SqlParameter  
  42.         Get 
  43.             Dim tPr As New SqlParameter  
  44.             If m_PName <> "" Then 
  45.                 tPr.ParameterName = m_PName  
  46.                 'tPr.TypeName = m_PTypeName  
  47.                 Select Case m_PTypeName.ToString.ToLower  
  48.                     Case "string" 
  49.                         tPr.Value = CType(m_PValue, String)  
  50.  
  51.                     Case "integer" 
  52.                         tPr.Value = CType(m_PValue, Integer)  
  53.  
  54.                     Case "decimal" 
  55.                         tPr.Value = CType(m_PValue, Decimal)  
  56.                 End Select 
  57.  
  58.             End If 
  59.             Return tPr  
  60.         End Get 
  61.  
  62.     End Property 
  63.  
  64. End Class 

 

讀取的元件內容:

準備工作大致告一段落,接著終於要來撰寫資料存取的元件。首先是讀取的部分,相關程式碼如下:

  1. Imports System.EnterpriseServices  
  2. Imports System.Runtime.InteropServices  
  3. Imports System.Data.SqlClient  
  4. Imports System.Data  
  5.  
  6.  
  7. <Guid("75A6EE3A-C86F-4C48-A39C-026B9D7B4141"), _  
  8. EventTrackingEnabled(True)> _  
  9. Public Class CDMTDT00  
  10.     Inherits ServicedComponent  
  11.     '無Transaction的資料存取元件  
  12.  
  13.     Const DBName As String = "DMT" 
  14.  
  15.     Public Function GetDataTable(ByVal SqlTxt As String, ByVal Parameters As List(Of CDMTParameters)) As DataTable  
  16.         '有Parameter的讀取資料並透過DataTable傳回一批資料。  
  17.         Try 
  18.             Dim Dt As New DataTable  
  19.             Dim oConnStr As New CDMTConn  
  20.             Dim ConnStr As String = oConnStr.GetConnStr(DBName)  
  21.             Using Conn As New SqlConnection(ConnStr)  
  22.                 Using Cmmd As New SqlCommand(SqlTxt, Conn)  
  23.                     If Parameters.Count > 0 Then 
  24.                         Dim y As Integer 
  25.                         For y = 0 To Parameters.Count - 1  
  26.                             Cmmd.Parameters.Add(Parameters.Item(y).sParameter)  
  27.                         Next 
  28.                     End If 
  29.                     Dim Da As New SqlDataAdapter(Cmmd)  
  30.                     Da.Fill(Dt)  
  31.                 End Using  
  32.             End Using  
  33.             Return Dt  
  34.  
  35.         Catch ex As Exception  
  36.             Throw New Exception(ex.Message)  
  37.         End Try 
  38.     End Function 
  39.  
  40.     Public Function GetDataTable(ByVal SqlTxt As String) As DataTable  
  41.         '無Parameter的讀取資料並透過DataTable傳回一批資料  
  42.         Try 
  43.             Dim Dt As New DataTable  
  44.             Dim oConnStr As New CDMTConn  
  45.             Dim ConnStr As String = oConnStr.GetConnStr(DBName)  
  46.             Using Conn As New SqlConnection(ConnStr)  
  47.                 Using Cmmd As New SqlCommand(SqlTxt, Conn)  
  48.                     Dim Da As New SqlDataAdapter(Cmmd)  
  49.                     Da.Fill(Dt)  
  50.                 End Using  
  51.             End Using  
  52.             Return Dt  
  53.  
  54.         Catch ex As Exception  
  55.             Throw New Exception(ex.Message)  
  56.         End Try 
  57.     End Function 
  58.  
  59.  
  60.     Public Function ChkDataExist(ByVal SqlTxt As String, ByVal Parameters As List(Of CDMTParameters)) As Boolean 
  61.         '檢查資料是否存在  
  62.         Try 
  63.             Dim Rc As Boolean = False 
  64.             Dim oConnStr As New CDMTConn  
  65.             Dim ConnStr As String = oConnStr.GetConnStr(DBName)  
  66.             Using Conn As New SqlConnection(ConnStr)  
  67.                 Conn.Open()  
  68.                 Using Cmmd As New SqlCommand(SqlTxt, Conn)  
  69.                     If Parameters.Count > 0 Then 
  70.                         Dim y As Integer 
  71.                         For y = 0 To Parameters.Count - 1  
  72.                             Cmmd.Parameters.Add(Parameters.Item(y).sParameter)  
  73.                         Next 
  74.                     End If 
  75.                     Dim Dr As SqlDataReader = Cmmd.ExecuteReader  
  76.                     If Dr.HasRows Then 
  77.                         Rc = True 
  78.                     Else 
  79.                         Rc = False 
  80.                     End If 
  81.                     Dr.Close()  
  82.                 End Using  
  83.             End Using  
  84.             Return Rc  
  85.  
  86.         Catch ex As Exception  
  87.             Throw New Exception(ex.Message)  
  88.         End Try 
  89.     End Function 
  90. End Class 

以上這些只是初步的測試,小喵心想,應該還要個傳入SQL語法並傳回一個DataSet的Function。不過這個部分就帶下回有機會再分享。

 

維護的元件內容:

  1. Imports System.EnterpriseServices  
  2. Imports System.Runtime.InteropServices  
  3. Imports System.Data.SqlClient  
  4. Imports System.Data  
  5.  
  6.  
  7. <Guid("9007AB8A-CB61-43E8-8405-062695E0FFE5") _  
  8. , Transaction(TransactionOption.Required) _  
  9. , Synchronization(SynchronizationOption.Required) _  
  10. , JustInTimeActivation(True) _  
  11. , EventTrackingEnabled(True)> _  
  12. Public Class CDMTDT01  
  13.  
  14.     Inherits ServicedComponent  
  15.  
  16.     <AutoComplete()> _  
  17.     Public Function ExecChange(ByVal SqlTxt As String, ByVal Parameters As List(Of CDMTParameters)) As String 
  18.  
  19.         Try 
  20.             Dim oConnStr As New CDMTConn  
  21.             Dim ConnStr As String = oConnStr.GetConnStr("DMT")  
  22.             Using Conn As New SqlConnection(ConnStr)  
  23.                 Conn.Open()  
  24.                 Using Cmmd As New SqlCommand(SqlTxt, Conn)  
  25.                     If Parameters.Count > 0 Then 
  26.                         Dim y As Integer 
  27.                         For y = 0 To Parameters.Count - 1  
  28.                             Cmmd.Parameters.Add(Parameters.Item(y).sParameter)  
  29.                         Next 
  30.                     End If 
  31.                     Cmmd.ExecuteNonQuery()  
  32.                 End Using  
  33.             End Using  
  34.             Return "Success" 
  35.  
  36.         Catch ex As Exception  
  37.             Throw New Exception(ex.Message)  
  38.         End Try 
  39.     End Function 
  40.  
  41.     <AutoComplete()> _  
  42.     Public Function ExecChange(ByVal SqlTxt As String, ByVal Parameters As List(Of CDMTParameters), ByRef ChgCnt As Integer) As String 
  43.         Try 
  44.             Dim oConnStr As New CDMTConn  
  45.             Dim ConnStr As String = oConnStr.GetConnStr("DMT")  
  46.             Using Conn As New SqlConnection(ConnStr)  
  47.                 Conn.Open()  
  48.                 Using Cmmd As New SqlCommand(SqlTxt, Conn)  
  49.                     If Parameters.Count > 0 Then 
  50.                         Dim y As Integer 
  51.                         For y = 0 To Parameters.Count - 1  
  52.                             Cmmd.Parameters.Add(Parameters.Item(y).sParameter)  
  53.                         Next 
  54.                     End If 
  55.                     ChgCnt = Cmmd.ExecuteNonQuery()  
  56.                 End Using  
  57.             End Using  
  58.             Return "Success" 
  59.  
  60.         Catch ex As Exception  
  61.             Throw New Exception(ex.Message)  
  62.         End Try 
  63.     End Function 
  64.  
  65. End Class 

以上程式撰寫完成後,當然還須將元件註冊到元件服務中,註冊的部份請參考以前小喵所撰寫的這篇【N-Tiers開發方式(COM+元件的註冊、修改)

末記:這樣真是資料存取層嗎??

其實這樣的方式,小喵個人覺得並非完整的資料存取層。小喵個人覺得資料存取層的特性有以下:

  1. 資料存取層不包含任何的商業邏輯(這點可以符合)
  2. 可以抽出成為實體不同主機的分層(這點也可以)
  3. 資料庫更換時,只需修改資料存取層,不需修改商業邏輯層或者介面層的程式(並不符合)

小喵的方式,第三個並不符合。因為所有的SQL語法還是從商業邏輯層組好後,往這裡丟過來運作,然而不同的資料庫,盡管都是用SQL語法,但是語法上或多或少還是有差異,因此更換資料庫的時候,還是必須修改商業邏輯層裡面的SQL語法。小喵曾想過,或許可以直接把LINQ用LINQ包裝後,讓商業邏輯層透過LINQ的方式存取資料庫。不過小喵測試撰寫維護時還是會有些問題。似乎沒有辦法將LINQ在COM+多層架構運用的很好。這點小喵還要進一步研究。

寫到這邊到此稍作擱筆,下面還要介紹商業邏輯、展現層的相關程式,再來看怎麼實際運作成果。

 


以下是簽名:


Microsoft MVP
Visual Studio and Development Technologies
(2005~2019/6) 
topcat
Blog:http://www.dotblogs.com.tw/topcat