共用方式為


在 ASP.NET 2.0 和 ADO.NET 2.0 中撰寫一般資料存取程式碼

 

Dr. Shahram Khosravi
資訊解決方案

2005 年 4 月

適用於:

   Microsoft ADO.NET 2.0
   Microsoft ASP.NET 2.0
   Microsoft .NET Framework 2.0
   Microsoft Visual Web Developer 2005 Express Edition Beta
   C# 程式設計語言

總結: 使用逐步方法瞭解如何使用不同的 ASP.NET 2.0 和 ADO.NET 2.0 工具和技術來撰寫一般資料存取程式碼。 (18 個列印頁面)

下載相關聯的範例程式碼: GenericDataAccessSample.exe

簡介

大部分的 Web 應用程式都包含資料存取碼來存取基礎資料存放區,以執行基本資料作業,例如 SelectUpdateDeleteInsert。 本文使用逐步方法來示範頁面開發人員如何利用不同的 ASP.NET 2.0 和 ADO.NET 2.0 工具和技術來撰寫可用來存取不同類型的資料存放區的一般資料存取程式碼。 撰寫一般資料存取程式碼在資料驅動 Web 應用程式中特別重要,因為資料來自許多不同的來源,包括 Microsoft SQL Server、Oracle、XML 檔、一般檔案和 Web 服務,只是為了命名一些。

本文使用簡單的 Web 應用程式做為這裡呈現之所有程式碼的測試台。 應用程式包含兩個部分:第一個部分可讓系統管理員將電子報傳送給郵寄清單的所有訂閱者。 第二個部分可讓使用者訂閱或取消訂閱郵寄清單。 本文的第一個部分從實作簡單的資料存取代碼開始, (請參閱圖 1) 來存取 Microsoft SQL Server並擷取訂閱者清單。 程式碼會經過修改,並在文章的課程中更泛型。

圖 1. GetSubscribers 方法會擷取所有訂閱者的清單。

public IEnumerable GetSubscribers()
{
    SqlConnection con = new SqlConnection();
    con.ConnectionString = @"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf";

    SqlCommand com = new SqlCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    SqlDataAdapter ad = new SqlDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

由於圖 1 中的資料存取碼包含用來建立 ADO.NET 物件的程式碼,例如 SqlConnectionSqlCommandSqlDataAdapter 實例。 資料存取碼無法用來從其他資料存放區擷取訂閱者清單,例如 Oracle 資料庫。 頁面開發人員必須在每次需要存取新的資料存放區時,使用 GetSubscribers) 方法來修改資料存取程式碼 (。 接下來,請參閱 ADO.NET 2.0 如何使用提供者模式,協助頁面開發人員撰寫一般資料存取碼來存取不同類型的資料存放區。

ADO.NET 2.0 中的提供者模式

GetSubscribers 方法的主要問題是它包含用來建立 ADO.NET 物件的程式碼。 根據提供者模式,資料存取程式碼必須委派將程式碼提供給另一個類別建立 ADO.NET 物件的責任。 我將此類別稱為「程式碼提供者類別」,因為它提供用來建立 ADO.NET 物件的程式碼。 程式碼提供者類別會公開 CreateConnectionCreateCommandCreateDataAdapter等方法,其中每個方法都會提供程式碼來建立對應的 ADO.NET 物件。

由於程式碼提供者類別包含實際的程式碼,因此無法使用相同的類別來存取不同的資料存放區。 因此,資料存取碼 (GetSubscribers 方法) 必須修改並重新設定,以在每次用來存取新的資料存放區時,將程式碼提供給新程式碼提供者類別的責任委派給。 即使 GetSubscribers 方法不包含程式碼,仍會系結至程式碼。

提供者模式提供此問題的解決方案,並包含下列步驟:

  1. 設計和實作抽象基底提供者類別。

  2. 從抽象基底提供者類別衍生所有程式碼提供者類別。

  3. 讓資料存取程式碼 (GetSubscribers 方法) 使用抽象基類,而不是個別的程式碼提供者類別。

    抽象基類會委派將程式碼提供給適當的子類別建立 ADO.NET 物件的責任。 抽象基類名為 DbProviderFactory。 以下提供此類別的一些方法:

    public abstract class DbProviderFactory
    {
            public virtual DbConnection CreateConnection();
            public virtual DbCommand CreateCommand();
            public virtual DbDataAdapter CreateDataAdapter();
    }
    

    每個子類別都提供程式碼,以針對特定資料存放區建立適當的 ADO.NET 物件。 例如,SqlClientFactory子類別提供程式碼來建立 ADO.NET 物件以存取 Microsoft SQL Server,如圖 2 所示。

    圖 2. SqlClientFactory 類別及其一些方法

    public class SqlClientFactory : DbProviderFactory
    {
            public override DbConnection CreateConnection()
            {
                    return new SqlConnection();
            }
    
           public override DbCommand CreateCommand()
           {
                    return new SqlCommand();
           }
    
           public override DbDataAdapter CreateDataAdapter()
           {
                 return new SqlDataAdapter();
           }
    }
    

提供者模式可讓資料存取程式碼處理所有子類別,因為它們都是相同基類的所有子類別。 就資料存取程式碼而言,所有子類別都是 DbProviderFactory 類型。 資料存取碼無法得知所使用的子類別特定類型。 這會產生新的問題。 如果資料存取碼 (GetSubscribers 方法) 不知道子類別的類型,該如何接著具現化子類別的實例?

此問題的提供者模式解決方案包含下列三個部分:

  1. 唯一字串可用來識別每個子類別。 ADO.NET 2.0 會使用子類別的命名空間做為其唯一字串識別碼。例如,唯一字串識別碼的 System.Data.SqlClientSystem.Data.OracleClient 分別識別 SqlClientFactoryOracleClientFactory 子類別。
  2. 文字檔通常 (XML 檔案) 用來儲存所有子類別的相關資訊。 ADO.NET 2.0 會使用machine.config和web.config檔案來儲存必要的資訊。 子類別的相關資訊包含子類別的唯一字串識別碼和子類別類型的名稱。 例如,SqlClientFactory 子類別的相關資訊包括唯一字串識別碼 System.Data.SqlClient ,以及子類別類型的名稱,例如 System.Data.SqlClient.SqlClientFactory
  3. 靜態方法是設計和實作的。 方法可以是抽象基類的一部分,或屬於個別類別的一部分。 ADO.NET 2.0 會使用名為 DbProviderFactories 的個別類別來公開 GetFactory 靜態方法。 方法會採用所需子類別的唯一字串識別碼作為唯一引數,並搜尋具有指定唯一字串識別碼之子類別的machine.config檔案。方法會擷取所需子類別類型的名稱,並使用反映動態建立子類別的實例。

資料存取碼 (GetSubscribers 方法) 呼叫 GetFactory 靜態方法,並傳遞適當的唯一字串識別碼來存取對應子類別的實例。 GetSubscribers 方法存取 實例之後,它會呼叫適當的建立方法,例如 CreateConnection () 、CreateCommand () 等,以具現化適當的 ADO.NET 物件,如圖 3 所示。

圖 3. 使用新 ADO.NET 提供者模式的 GetSubscribers 方法版本

public IEnumerable GetSubscribers()
{
    DbProviderFactory provider = DbProviderFactories.GetFactory("System.Data.SqlClient");
    DbConnection con = provider.CreateConnection();
    con.ConnectionString = @"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf";
    DbCommand com = provider.CreateCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    DbDataAdapter ad = provider.CreateDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

資料存取程式碼 (GetSubscribers 方法) 委派為 GetFactory 方法具現化和傳回的程式碼提供者類別實例提供程式碼建立 ADO.NET 物件的責任。 因此,相同的資料存取碼可用來存取不同的資料存放區,例如 Microsoft SQL Server 和 Oracle。

建立正確 ADO.NET 物件的程式碼是資料存放區專屬的程式碼。 ADO.NET 2.0 中的提供者模式會從資料存取程式碼中移除這些資料存放區特定部分, (GetSubscribers 方法) ,使其更泛型。 不過,提供者模式不會移除所有資料存放區特定部分。 GetSubscribers 方法的更仔細檢查會顯示下列剩餘的資料存放區特定部分:

  1. 連接字串
  2. 識別基礎程式碼提供者類別的唯一字串識別碼
  3. 命令文字
  4. 命令類型

除非對上述部分執行某些動作,否則資料存取程式碼仍然系結至特定類型的資料存放區。 ADO.NET 2.0 中的提供者模式無法協助解決此問題。 不過,ADO.NET 2.0 提供其他工具和技術,以從 GetSubscribers 方法移除前兩個數據存放區特定部分,例如連接字串和唯一字串識別碼。

連接字串

連接字串是 Web 應用程式中一些最有價值的資源。 它們很重要,.NET Framework 2.0 會將它們視為「第一級公民」。 web.config檔案現在支援名為< connectionStrings >的新區段,其中包含應用程式中使用的所有連接字串。 因此,我們會將連接字串從 GetSubscribers 方法移至本節:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
          <add 
            name="MySqlConnectionString" 
            connectionString="Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
      providerName="System.Data.SqlClient"/>
    </connectionStrings>
</configuration>

connectionStrings > 元素的 <add > 子項目會公開下列三個重要屬性: <

  • 名稱- 連接字串的易記名稱
  • connectionString - 實際連接字串
  • providerName — 程式碼提供者類別的唯一字串識別碼或不變異

NET Framework 2.0 提供資料存取程式碼 (GetSubscribers 方法,) 正確的工具,以一般方式從web.config檔案擷取連接字串值,如下所述。 .NET Framework 2.0 中的System.Configuration命名空間包含名為 Configuration 的新類別。 這個類別代表web.config或machine.config檔案的整個內容。 資料存取碼無法使用新的運算子直接建立這個類別的實例。

類別本身會公開名為 GetWebConfiguration 的靜態方法,該方法會採用web.config檔案的路徑,並傳回 Configuration 類別的 實例,此實例代表web.config檔案的整個內容:

Configuration configuration = Configuration.GetWebConfiguration("~/");

繼承自 ConfigurationSection 類別的類別代表web.config檔案的每個區段。 類別的名稱是由區段的名稱加上關鍵字 Section 所組成。 例如,ConnectionStringsSection類別代表web.config檔案之 connectionStrings > 區段的內容 < 。 (GetSubscribers 方法的資料存取代碼) 無法使用新的運算子直接建立 ConnectionStringsSection 類別的實例。 Configuration 類別會公開名為 Sections 的集合屬性,其中包含代表web.config檔案不同區段的所有物件:

ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];

由於 Sections 是 ConfigurationSection 物件的集合,因此資料存取碼必須輸入轉換傳回的實例。 GetSubscribers 方法存取 ConnectionStringsSection 物件之後,並使用它來存取連接字串值:

string connectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;

圖 4 顯示新版本的 GetSubscribers 方法,其中包含以一般方式擷取連接字串所需的程式碼。

圖 4. 從web.config檔案擷取連接字串的 GetSubscribers 方法版本

public IEnumerable GetSubscribers()
{
    DbProviderFactory provider = DbProviderFactories.GetFactory("System.Data.SqlClient");
    DbConnection con = provider.CreateConnection();

    Configuration configuration = Configuration.GetWebConfiguration("~/");
    ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];
    con.ConnectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;

    DbCommand com = provider.CreateCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    DbDataAdapter ad = provider.CreateDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

GetSubscribers 方法現在包含 MySqlConnectionString 字串,因此我們仍然需要修改 GetSubscribers 方法,以新增對不同資料存放區的支援,例如 Oracle 資料庫。 我們似乎回到正方形。 不完全是。 我們將連接字串從資料存取程式碼移至web.config檔案,獲得一些重要的優點:

  • 連接字串現在是 web.config 檔案之 connectionStrings 元素之 add > 子項目之 connectionString 屬性 < 的值,這是 XML 檔。 XML 檔的絕佳事項是我們可以加密檔中的單一元素。 如果我們只需要保護部分檔,就不需要加密整個檔。 .NET Framework 2.0 隨附的工具,可讓我們加密 < connectionStrings > 區段,以保護我們最重要的資源連接字串。 假設駭客在連接字串上取得其手部時,駭客可以對寶貴的資料庫執行多少損害。 請記住,連接字串全都是駭客需要存取我們的資料庫。

  • 我們似乎已完成的一切都是取代下列字串

    "Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
    

    使用新的字串 MySqlConnectionString。 不過,有一大差異。 前一個字串包含SQL Server資料庫特定資訊,這些資訊不適用於另一個資料庫,例如 Oracle,但後者字串只是易記名稱。

不過,易記名稱仍會造成問題,因為它參考web.config檔案之 connectionStrings > 區段中的特定連接字串 < 。 在我們的範例中,它會參考用來存取 Microsoft SQL Server的連接字串。 這表示 GetSubscribers 方法 (必須修改資料存取碼) ,才能使用不同的易記名稱來存取不同的資料存放區,例如 Oracle。

若要避免修改資料存取程式碼,我們可以將易記名稱從資料存取程式碼< 移至web.config檔案的 appSettings >區段,並在執行時間動態擷取資料存取程式碼,如下所示:

string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];

我們也會將提供者名稱 < 移至 appSettings > 區段:

string providerName = ConfigurationSettings.AppSettings["ProviderName"];

頁面開發人員只要將 appSettings > 元素的子 < 元素的值屬性 <> 變更為相同的資料存取碼,即可存取不同的資料存放區,而不需對資料存取程式碼本身進行任何變更。

圖 5 顯示包含最近變更之 GetSubscribers 方法 (資料存取代碼的版本) 。

圖 5. GetSubscribers 方法的版本,可從web.config檔案中擷取提供者名稱和連接字串的易記名稱

public IEnumerable GetSubscribers()
{
    string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];
    string providerName = ConfigurationSettings.AppSettings["ProviderName"];
    Configuration configuration = Configuration.GetWebConfiguration("~/");
    ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];

    

    DbProviderFactory provider = DbProviderFactories.GetFactory(providerName);
    DbConnection con = provider.CreateConnection();
    con.ConnectionString = section.ConnectionStrings[connectionStringName].ConnectionString;

    DbCommand com = provider.CreateCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    DbDataAdapter ad = provider.CreateDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

資料來源控制項

圖 5 所示的 GetSubscribers 方法最新版本仍然不是泛型,因為發生下列問題:

  • 方法包含資料存放區—特定資訊,也就是命令文字和命令類型。 因此,頁面開發人員仍然需要修改 方法,才能使用它來存取不同的資料庫。
  • 方法會將 DataView 類別的實例傳回給其呼叫端,以便方法摘要其呼叫端表格式資料。 我們可以將此視為 GetSubscribers 方法與其呼叫端之間的合約。 呼叫端預期 GetSubscribers 方法在所有情況下都接受合約,即使基礎資料存放區本身不是表格式也一樣。 由於 GetSubscribers 方法會使用 ADO.NET 物件來存取基礎資料存放區,因此無法存取階層式資料存放區,例如 XML 檔案,其中必須使用System.Xml及其子命名空間中的類別實例,而不是 ADO.NET 物件。

GetSubscribers 方法中使用的資料存取程式碼主要問題是,它直接包含從基礎資料存放區擷取資料的實際程式碼。 這是.NET Framework 2.0 提供者模式特別設計來解決的問題類型。 根據提供者模式,GetSubscribers 方法必須委派提供程式碼以存取不同類別之資料存放區的責任。 它稱為程式碼提供者類別。 程式碼提供者類別的類型取決於其存取的資料存放區類型。 這些程式碼提供者類別統稱為資料來源控制項。 ASP.NET 2.0 隨附數種不同類型的資料來源控制項,包括 SqlDataSourceAccessDataSourceObjectDataSourceXmlDataSourceSiteMapDataSource 控制項。

SqlDataSource 控制項特別設計來更新、刪除、插入和擷取關聯式資料存放區中的資料,例如 Microsoft SQL Server 和 Oracle。 AccessDataSource 控制項是 SqlDataSource 控制項的子類別,可瞭解如何使用 Microsoft Access 資料庫。 另一方面,ObjectDataSource 控制項會使用記憶體內部商務物件作為其資料存放區。 XmlDataSource 控制項特別設計用來從 XML 檔擷取資料。 不過,XmlDataSource 控制項不提供基礎 XML 檔的寫入權限。

每個資料來源控制項都會公開其基礎資料存放區的一或多個檢視。 每個檢視都是適當類別的實例。 例如,SqlDataSource、AccessDataSource 和 ObjectDataSource 控制項會分別公開 SqlDataSourceViewAccessDataSourceViewObjectDataSourceView 類別實例的檢視。 檢視會隱藏基礎資料存放區的實際類型,並使它就像資料存取程式碼預期的類型一樣。 例如,GetSubscribers 方法需要表格式資料存放區類型,因為它會摘要其用戶端表格式資料。 表格式檢視可讓 GetSubscribers 方法從基礎資料存放區擷取表格式資料,即使資料存放區本身是階層式資料來源,例如 XML 檔也一樣。 這可讓 GetSubscribers 方法將表格式和階層式資料存放區視為表格式資料存放區。

資料來源控制項可以提供其用戶端兩種類型的檢視:表格式和階層式。 ASP.NET 2.0 隨附兩種資料來源控制項,可提供兩種類型的檢視、XmlDataSource 和 SiteMapDataSource。 資料來源控制項的其餘部分:SqlDataSource、AccessDataSource 和 ObjectDataSource,只有表格式檢視。 不過,可以擴充它們以提供表格式和階層式檢視。

表格式資料來源控制項

表格式資料來源控制項讓它的基礎資料存放區就像表格式資料存放區一樣,不論資料存放區是否為表格式。 表格式資料存放區包含資料列和資料行的資料表,其中每個資料列都代表資料項目。 資料表的名稱可唯一識別並找出表格式資料存放區中其他資料表的資料表。 表格式檢視的作用就像資料表,這表示檢視的名稱。

如先前所述,每個資料來源控制項類別及其相關聯的檢視類別 (例如 SqlDataSource 類別及其相關聯的 SqlDataSourceView 類別) 提供實際程式碼,以便從基礎資料存放區更新、刪除、插入和擷取資料。 很明顯地,每個資料來源控制項及其相關聯的檢視類別的程式碼都是特別設計來使用特定類型的資料存放區。 因此,每個資料來源控制項類別及其相關聯的檢視類別都是資料存放區特定的。 這會對 GetSubscribers 方法造成嚴重問題,該方法會使用資料來源控制項及其表格式檢視來存取其基礎資料存放區,因為它會將 方法系結至特定類型的資料存放區,這表示相同的方法無法用來存取不同類型的資料存放區。

ASP.NET 2.0 提供使用提供者模式的解決方案:

  1. 介紹 IDataSource 介面和 DataSourceView 抽象類別
  2. 從 IDataSource 介面衍生所有表格式資料來源控制項
  3. 從 DataSourceView 抽象類別衍生所有表格式檢視

IDataSource 介面和 DataSourceView 抽象類別會委派提供實際程式碼來更新、刪除、插入和擷取資料至其適當子類別的責任。 GetSubscribers 方法之類的資料存取程式碼必須使用 IDataSource 介面和 DataSourceView 抽象類別的方法和屬性。 它們不得使用特定資料來源控制項類別特有的任何方法或屬性,例如 SqlDataSource 或 SqlDataSourceView 之類的特定資料來源檢視類別。 提供者模式可讓資料存取程式碼以一般方式處理所有資料來源控制項及其各自的資料來源檢視。 就資料存取程式碼而言,所有資料來源控制項的類型都是 IDataSource,而所有資料來源檢視的類型都是 DataSourceView。 資料存取程式碼無法得知所使用的資料來源控制項和資料來源檢視物件的實際類型。 這會導致新問題。 如果資料存取碼 (GetSubscribers 方法) 不知道資料來源控制項的類型,該如何接著具現化它的實例?

如先前所述,提供者模式提供包含下列步驟的解決方案:

  1. 唯一字串識別碼可用來識別每個資料來源控制項類別。
  2. 文字檔通常 (XML 檔案) 用來儲存所有資料來源控制項類別的相關資訊。
  3. 設計並實作機制,可搜尋 XML 檔案中具有指定字串識別碼的子類別。

現在讓我們看看 ASP.NET 2.0 如何實作提供者模式的上述三項工作。 ASP.NET 2.0 衍生自 Control 類別的所有資料來源控制項。 如果資料來源控制項未轉譯 HTML 標籤文字,為什麼資料來源控制項衍生自 Control 類別? 它們衍生自 Control 類別,因此可以繼承下列三個重要功能:

  1. 它們可以以宣告方式具現化。
  2. 他們會跨回傳儲存和還原其屬性值。
  3. 它們會新增至包含頁面的控制樹狀結構。

第一項功能可讓頁面開發人員在其各自的 .aspx 檔案中以宣告方式具現化資料來源控制項。 因此,.aspx 檔案可作為提供者模式第二個步驟所需的文字或 XML 檔案。 ASP.NET 2.0 控制項架構會動態建立宣告資料來源控制項的實例,並將實例指派給名稱為宣告資料來源控制項之 ID 屬性值的變數。 這會處理提供者模式所需的上述第一和第三個步驟。

圖 6 顯示 GetSubscribers 方法的版本,其使用 IDataSource 介面和 DataSourceView 抽象類別的方法和屬性來存取基礎資料存放區:

圖 6. 使用 IDataSource 介面和 DataSourceView 抽象類別之方法和屬性的 GetSubscribers 方法版本

void GetSubscribers()
{
    IDataSource ds = (IDataSource)MySource;
    DataSourceView dv = ds.GetView(String.Empty);
    DataSourceSelectArguments args = new DataSourceSelectArguments();
    if (dv.CanSort)
        args.SortExpression = "Email";
    DataSourceViewSelectCallback callback = new DataSourceViewSelectCallback(SendMail);
    dv.Select(args, callback);
}

GetSubscribers 方法的第一行清楚顯示方法會將資料來源控制項視為 IDataSource 類型的物件。 方法不會和不應該關心資料來源控制項的實際類型,例如它是否為 SqlDataSource、AccessDataSource、ObjectDataSource 或 XmlDataSource 控制項。 這可讓頁面開發人員從一個資料來源控制項切換到另一個資料來源,而不需在 GetSubscribers 方法) 修改資料存取 (代碼。 下一節將更詳細地討論此重要問題。

GetSubscribers 方法會呼叫 IDataSource 物件的 GetView 方法,以存取其預設表格式檢視物件。 請注意,GetView 方法會傳回 DataSourceView 類型的物件。 GetSubscribers 方法不會且不應該關心檢視物件的實際類型,例如它是否為 SqlDataSourceView、AccessDataSourceView、ObjectDataSourceView 或 XmlDataSourceView 物件。

接下來,GetSubscribers 方法會建立 DataSourceSelectArguments 類別的實例,以要求額外的作業,例如插入、分頁或擷取 Select 作業傳回的資料列總數。 方法首先必須檢查 DataSourceView 類別之 CanInsertCanPageCanRetrieveTotalRowCount 屬性的值,以確保檢視物件在提出要求之前支援個別的作業。

由於 Select 作業是非同步,所以 GetSubscribers 方法會將 SendMail 方法註冊為回呼。 Select 方法會在查詢資料並傳遞資料做為其引數之後,自動呼叫 SendMail 方法,如圖 7 所示。

圖 7. SendMail 方法會列舉資料並擷取必要的資訊。

void SendMail(IEnumerable data)
{
    string firstName = String.Empty;
    string lastName = String.Empty;
   
    IEnumerator iter = data.GetEnumerator();
    while (iter.MoveNext())
    {
        MailMessage message = new MailMessage();
        message.From = "admin@greatnews.com";
        message.To = DataBinder.Eval(iter.Current, "Email").ToString();
        message.Subject = "NewsLetter";
        firstName = DataBinder.Eval(iter.Current, "FirstName").ToString();
        lastName = DataBinder.Eval(iter.Current, "LastName").ToString();
        string mes = "Dear " + firstName + " " + lastName + ",<br/>";
        mes += MessageBody.Text;
        message.Body = mes;
        message.BodyFormat = MailFormat.Html;
      SmtpMail.SmtpServer = "<myserver>";
      SmtpMail.Send(message);
    }
}

SendMail 方法會呼叫傳入之物件的 GetEnumerator 方法做為其第一個引數,以存取其列舉值物件,並使用列舉值列舉資料。 SendMail 方法會使用DataBinder類別的Eval方法,擷取每個訂閱者的電子郵件地址、名字和姓氏,並將新聞信件傳送給每一個訂閱者。

從一個資料來源控制項切換到另一個

如先前所述,ASP.NET 控制項架構會動態建立在個別 .aspx 頁面中宣告的資料來源控制項實例,並將它指派給名稱為宣告資料來源控制項之 ID 屬性值的變數。 這類宣告資料來源控制項的動態具現化會將 GetSubscribers 方法與資料來源控制項的實際類型隔離,並允許 方法將所有資料來源控制項視為 IDataSource 類型的物件。 這可讓頁面開發人員在不修改 GetSubscribers 方法) (資料存取碼的情況下,從某個類型的資料來源控制項切換到另一種。 本節提供這類案例的範例。

假設我們的 Web 應用程式使用 GetSubscribers 方法搭配 ObjectDataSource 控制項,從關聯式資料存放區擷取訂閱者清單,例如 Microsoft SQL Server:

<asp:SqlDataSource ID="MySource" Runat="Server"
ConnectionString="<%$ ConnectionStrings:MySqlConnectionString %>"
SelectCommand="Select * From Subscribers" />

假設我們的 Web 應用程式在訂閱者清單可能也來自 XML 檔的環境中運作。 XML 檔可能是本機 XML 檔案或透過 URL 存取的遠端資源。 因此,我們的應用程式必須能夠從 XML 檔擷取訂閱者清單。 顯然,ObjectDataSource 控制項不是設計來從 XML 檔擷取表格式資料,這表示我們必須使用可從 XML 檔擷取表格式資料的資料來源控制項,例如 XmlDataSource 控制項。

GetSubscribers 方法不會使用 ObjectDataSource 和 ObjectDataSourceView 類別特有的任何屬性或方法,而且只會使用 IDataSource 介面和 DataSourceView 抽象類別的方法和屬性來處理資料來源控制項。 我們可以輕鬆地從 ObjectDataSource 切換至 XmlDataSource 控制項,並使用與 GetSubscribers 方法相同的資料存取碼來擷取訂閱者清單:

<asp:XmlDataSource ID="MySource" Runat="Server"
      DataFile="data.xml" XPath="/Subscribers/Subscriber" />

XmlDataSource 控制項的 XPath 屬性值會設定為 /Subscribers/Subscriber,以選取所有訂閱者。

插入和刪除作業

回想一下我們的 Web 應用程式包含兩個部分。 應用程式的第二個部分可讓使用者從郵寄清單中訂閱/取消訂閱。 按一下 [ 訂閱 ] 按鈕時會呼叫 Subscribe 方法,如圖 8 所示。

圖 8. 按一下 [訂閱] 按鈕時,會呼叫 Subscribe 方法。

void Subscribe(Object sender, EventArgs e)
{
    IDataSource ds = (IDataSource)MySource;
    DataSourceView dv = ds.GetView(String.Empty);
    KeyedList values = new KeyedList();
    values.Add("Email", Email.Text);
    values.Add("FirstName", FirstName.Text);
    values.Add("LastName", LastName.Text);
    DataSourceViewOperationCallback callback = new DataSourceViewOperationCallback(OperationCallback);
    if (dv.CanInsert)
        dv.Insert(values, callback);
}

Subscribe 方法的第一行顯示方法不會關心資料來源控制項的實際類型。 因此,我們可以切換到不同的資料來源控制項,以支援新的資料存放區,而不需要變更 Subscribe 方法中的程式碼。

方法會使用 KeyedList 類別的實例來收集訂閱者的電子郵件、名字和姓氏。 我們不需要使用 KeyedList 類別。 我們可以使用任何實作 IDictionary 介面的類別,包括 ArrayList、KeyedList 等。

Subscribe 方法會檢查資料來源檢視物件的 CanInsert 屬性值,以確保檢視物件支援 Insert 作業,再呼叫 Insert 方法。 Subscribe 方法會將 KeyedList 實例當做第一個引數傳遞至 Insert 方法。

Unsubscribe方法的運作方式類似于 Subscribe 方法。 主要差異在於 Unsubscribe 方法會呼叫個別檢視物件的 Delete 方法,以從基礎資料存放區中移除訂閱,如圖 9 所示。

圖 9. 按一下 [取消訂閱] 按鈕時,會呼叫 Unsubscribe 方法。

void Unsubscribe(Object sender, EventArgs e)
{
    IDataSource ds = (IDataSource)MySource;
    DataSourceView dv = ds.GetView(String.Empty);
    KeyedList keys = new KeyedList();
    keys.Add("Email", Email.Text);
    KeyedList oldValues = new KeyedList();
    oldValues.Add("Email", Email.Text);
    DataSourceViewOperationCallback callback = new DataSourceViewOperationCallback(OperationCallback);
    if (dv.CanDelete)
        dv.Delete(keys, oldValues, callback);
}

階層式資料來源控制項

GetSubscribers 方法 (資料存取程式碼,) 將表格式資料摘要給其呼叫端。 不過,有時候資料存取碼必須將階層式資料傳回給其呼叫端。 本節說明傳回階層式資料的 GetSubscribers 方法版本實作。 我們可以將此視為方法與其呼叫端之間的合約。 呼叫端預期方法會從階層式和表格式資料存放區傳回階層式資料。

ASP.NET 2.0 使用提供者模式,將 GetSubscribers 方法與基礎資料存放區的實際類型隔離,並以資料存放區的階層式檢視呈現方法。 這可讓 方法將階層式和表格式資料存放區視為階層式資料存放區。

每個階層式資料來源控制項都是特別設計來使用特定資料存放區。 不過,由於所有階層式資料來源控制項都會實作 IHierarchicalDataSource 介面,而且所有階層式資料來源檢視都衍生自 HierarchicalDataSourceView 類別,所以 GetSubscribers 方法不需要處理每個資料來源控制項的特定資料,而且可以一般方式處理所有。

IHierarchicalEnumerable GetSubscribers()
{
    IHierarchicalDataSource ds = (IHierarchicalDataSource)MySource;
    HierarchicalDataSourceView dv = ds.GetHierarchicalView("/Subscribers");
    return dv.Select();
}

GetSubscribers 方法的第一行顯示方法會將資料來源控制項視為 IHierarchicalDataSource 類型的物件,而且不關心資料來源控制項的實際類型。 這可讓頁面開發人員切換至新的階層式資料來源控制項,以新增對新資料存放區的支援,而不需要修改 GetSubscribers 方法中的程式碼。

GetSubscribers 方法接著會呼叫 HierarchicalDataSourceView 類別的 GetHierarchicalView 方法,以存取具有指定路徑的階層式檢視,例如 「/Subscribers」。 請注意 Select 方法不是非同步。 應用程式會將從 GetSubscribers 方法傳回的資料傳遞至 SendMail 方法, (請參閱圖 15) 。 請注意,資料的類型為 IHierarchicalEnumerable

IHierarchicalEnumerable 會實作 IEnumerable,這表示它會公開 GetEnumerator 方法。 SendMail 方法會呼叫 GetEnumerator 方法來存取個別的 IEnumerator 物件,該物件接著會用來列舉資料。 IHierarchicalEnumerable 也會公開名為 GetHierarchyData 的方法,這個方法會採用列舉的物件,並傳回與其相關聯的 IHierarchyData 物件。

IHierarchyData 介面會公開名為 Item的重要屬性,這並非資料項目。 SendMail 方法會使用XPathBinder類別的Eval方法,針對 Item 物件評估 XPath 運算式。

圖 10. SendMail 方法會列舉資料、擷取必要的資訊,並將電子報傳送給每個訂閱者。

void SendMail(IHierarchicalEnumerable data)
{
    string firstName = String.Empty;
    string lastName = String.Empty;

    IEnumerator iter = data.GetEnumerator();
    while (iter.MoveNext())
    {
        IHierarchyData ihdata = data.GetHierarchyData(iter.Current);
        MailMessage message = new MailMessage();
        message.From = "admin@subscribers.com";
        message.To = XPathBinder.Eval(ihdata.Item, "@Email").ToString();
        message.Subject = "NewsLetter";
        firstName = XPathBinder.Eval(ihdata.Item, "@FirstName").ToString();
        lastName = XPathBinder.Eval(ihdata.Item, "@LastName").ToString();
        string mes = "Hi " + firstName + " " + lastName + ",<br/>";
        mes += MessageBody.Text;
        message.Body = mes;
        message.BodyFormat = MailFormat.Html;
        SmtpMail.SmtpServer = "MyServer";
        SmtpMail.Send(message);
    }
}

結論

本文使用逐步方法顯示不同的 ASP.NET 2.0 和 ADO.NET 2.0 工具和技術,本文示範頁面開發人員如何撰寫可用來存取不同類型的資料存放區的一般資料存取程式碼。

Dr. Shahram Khosravi是 Schlumberger Information Solutions (SIS) 資深軟體工程師。Shahram 專門 ASP.NET、XML Web 服務、.NET 技術、XML 技術、3D 電腦圖形、HI/可用性、設計模式,以及開發 ASP.NET 伺服器控制項和元件。他有 10 年以上的物件導向程式設計經驗。他使用各種 Microsoft 工具和技術,例如SQL Server和 ADO.NET。Shahram 已撰寫 .NET 的文章,以及 asp.netPRO 雜誌的 ASP.NET 技術。