ObjectDataSource 在網頁控件和數據訪問組件間建立一個聲明性的鏈接。ObjectDataSource 非常靈活,並可以和多種類型的組件一起工作。
要使用它,你的數據訪問類必須遵守以下幾個規則:
- 所有邏輯必須包含在單個類中(如果使用不同的類選擇和更新數據,那么必須把它們封裝在一個更高層的類中)
- 調用單個方法后,它必須提供查詢結果
- 查詢結果必須是幾條記錄的組合,可以表現為集合、數組、DataSet、或實現 IEnumerable 的列表對象。每個記錄由一個自定義對象通過公用屬性公開它所有的數據
- 可以使用實例方法或靜態方法。不過,如果使用實例方法,類必須有一個默認的無參構造函數,以便 ObjectDataSource 可以創建所需的實例
- 必須是無狀態的。因為如果正在使用實例方法,那么 ObjectDataSource只在需要時才創建對象實例,並在每次請求結束后銷毀它。
通過處理 ObjectDataSource 事件以及編寫自定義代碼可以忽略上面很多條規則。但如果你希望你的數據訪問類能夠無需額外的工作就無縫插入數據綁定模型,那么應該遵守這些規則。
選擇記錄
例如有這樣一個自定義數據訪問組件:
public class EmployeeDB
{
private string conStr;
public EmployeeDB()
{
conStr = WebConfigurationManager.ConnectionStrings["NorthWind"].ConnectionString;
}
public EmployeeDB(string connectionString)
{
this.conStr = connectionString;
}
// 一些增刪改查的方法,調用數據庫相應的存儲過程
public int InsertEmployee(EmployeeDetails emp) { ...}
public EmployeeDetails GetEmployee(int employeeID) { ...}
public List<EmployeeDetails> GetEmployees() { ...}
public int InsertEmployee(EmployeeDetails emp){...}
public void DeleteEmployee(int employeeID) { ...}
// 省略......
}
在頁面中,使用這個類的第一步是定義 ObjectDataSource 並指定包含數據訪問類的名稱,可以通過 TypeName 屬性提供類的全名來指定:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" TypeName="Model.EmployeeDB">
</asp:ObjectDataSource>
下一步就是指向用於增刪改查的那些方法(ObjectDataSource 定義了 SelectMethod、InsertMethod、UpdateMethod、DeleteMethod 四個屬性):
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
TypeName="Model.EmployeeDB" SelectMethod="GetEmployees">
</asp:ObjectDataSource>
然后可以綁定網頁控件了:
<asp:ObjectDataSource ID="sourceEmployees" runat="server" TypeName="Model.EmployeeDB"
SelectMethod="GetEmployees"></asp:ObjectDataSource>
<asp:ListBox ID="ListBox1" runat="server" DataSourceID="sourceEmployees" DataTextField="FirstName"
DataValueField="EmployeeID"></asp:ListBox>
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="sourceEmployees">
</asp:GridView>
默認情況下,ObjectDataSource 在頁面上列的顯示順序是 EmployeeDetails 類中屬性的聲明順序。而 SqlDataSource 在頁面上列的顯示順序是查詢中的順序。外觀的相似掩蓋了幕后的真相,實際上場景是不同的。在這個示例中,網頁不需要硬編碼任何 SQL 語句,所有的工作都在 EmployeeDB 類中完成。
EmployeeDB 類使用錯誤處理塊以確保發生錯誤時連接被正確關閉,但它並沒有捕獲異常。這是正確的,因為最佳的設計實踐是讓異常通知到網頁,由頁面決定如何來更好的通知用戶。那么在頁面中,我們一樣可以處理 ObjectDataSource 的 Selected、Inserted、Updated、Deleted 事件,檢查異常,最后將異常標記為已處理。
1. 使用參數化構造函數
ObjectDataSource 默認只能創建沒有參數的構造函數的自定義訪問類,不過你可以響應 ObjectDataSource .ObjectCreating 事件讓 ObjectDataSource 和不符合這個條件的數據訪問類一起工作。
現在的 EmployeeDB 類直接從 web.config 文件中讀取數據庫連接字符串,像下面這樣:
private string conStr;
public EmployeeDB()
{
conStr = WebConfigurationManager.ConnectionStrings["NorthWind"].ConnectionString;
}
然而你可能再添加一個構造函數,作為讓網頁提供特定字符串的另外一種方法:
public EmployeeDB(string connectionString)
{
this.conStr = connectionString;
}
為了強制 ObjectDataSource 使用這個帶參的構造函數,需要處理 ObjectCreating 事件:
protected void sourceEmployees_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
{
e.ObjectInstance = new Model.EmployeeDB("......");
}
很顯然,這個事件中還可以執行更復雜的初始化工作。(例如,可以調用一個初始化方法來選擇創建它的某個子類等等)。
2. 使用方法參數
ListBox 提供所有雇員的 ID,並設置了自動回發:
<asp:ObjectDataSource ID="sourceEmployees" runat="server" TypeName="Model.EmployeeDB"
SelectMethod="GetEmployees"></asp:ObjectDataSource>
<asp:ListBox ID="ListBox1" runat="server" DataSourceID="sourceEmployees" DataTextField="EmployeeID"
DataValueField="EmployeeID" AutoPostBack="true"></asp:ListBox>
DetailsView 根據 ID 來獲取單個用戶的信息,因此要接收 ListBox 的選中記錄的值:
<asp:ObjectDataSource ID="sourceEmployee" runat="server" TypeName="Model.EmployeeDB"
SelectMethod="GetEmployee" onselecting="sourceEmployee_Selecting">
<SelectParameters>
<asp:ControlParameter ControlID="ListBox1" Name="employeeID" PropertyName="SelectedValue" />
</SelectParameters>
</asp:ObjectDataSource>
<asp:GridView ID="GridView1" runat="server" DataSourceID="sourceEmployee">
</asp:GridView>
ObjectDataSource 中定義的參數名必須要和將要調用的方法中的參數名完全一致。ObjectDataSource 調用方法時會利用反射檢查方法,參數以匹配。ObjectDataSource 支持方法的重載。
第一次請求頁面時,ListBox 控件不會有任何選定的值。但 DetailsView 仍然試圖執行綁定。employeeID 參數值為空值,但因為整形不可以為空值,所有真實參數值是 0 ,GetEmployee()方法執行查詢時找不到 ID 為 0 的記錄,這是一個錯誤的條件,因此會拋出一個異常。
可以修改 GetEmployee()方法來返回空值解決這個問題,但更有意義的做法是捕獲試圖綁定的事件並取消它的執行:
protected void sourceEmployee_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
{
if (e.InputParameters["employeeID"] == null)
{
e.Cancel = true;
}
}
更新記錄
ObjectDataSource 提供和 SqlDataSource 相同的更新數據綁定的支持。
<asp:ObjectDataSource ID="sourceEmployees" runat="server" TypeName="Model.EmployeeDB"
SelectMethod="GetEmployees" UpdateMethod="UpdateEmployee"></asp:ObjectDataSource>
保證 UpdateMethod 有正確的簽名很有挑戰性。更新、刪除、新增操作自動從鏈接的數據庫控件中獲得參數的集合,這些參數具有和相應類屬性相同的名字。
假設創建了一個網格並啟用了編輯,用戶提交更新時,GridView 為 EmployeeDetails 類的每個屬性創建一個參數(EmployeeID、FirstName、LastName、TitleOfCourtesy)並加入到 ObjectDataSource.UpdateParameters 集合。接着 ObjectDataSource 在 EmployeeDB 類中查找 UpdateEmployee()的方法,這個方法必須包含具有相同名字的相同參數。
也就是說,下面這個是匹配的:
public void UpdateEmployee(int employeeID,string firstName,string lastName,string titleOfCourtesy)
而這個不匹配,名字不完全相同:
public void UpdateEmployee(int id,string first,string last,string titleOfCourtesy)
這個也不匹配,有額外的參數:
public void UpdateEmployee(int employeeID,string firstName,string lastName,string titleOfCourtesy,bool useOpt)
提示:
- 方法匹配算法不區分大小寫
- 不考慮參數順序和數據類型
- 只尋找正確參數個數和相同參數名稱的方法,只要有這樣的方法,更新就會自動提交,無需任何自定義代碼。
使用數據對象執行更新
先前示例中的 UpdateEmployee()方法有一個問題,方法簽名略顯笨拙!既然已經定義了 EmployeeDetails 類,使用它的實例作為參數才是有意義的:
public void UpdateEmployee(EmployeeDetails emp)
ObjectDataSource 支持這一的方式,但必須設置 DataObjectTypeName 為要使用的類的全名:
<asp:ObjectDataSource ID="sourceEmployees" runat="server" TypeName="Model.EmployeeDB"
DataObjectTypeName="Model.EmployeeDetails"></asp:ObjectDataSource>
此外,你的數據對象必須遵守下面的規則:
- 必須有一個默認的無參構造函數
- 對於每個參數,必須有一個同名的屬性
- 所有屬性均為公有並可寫
1. 處理非標准的方法簽名
有時會碰到數據訪問類(例如 EmployeeDetails)的屬性名稱和更新方法(例如 EmployeeDB.UpdateEmployee())的參數名稱不完全匹配的問題。要解決這樣的問題,仍然是新建參數,其次捕獲更新事件,替換參數。示例如下:
<asp:ObjectDataSource ID="sourceEmployees" runat="server" SelectMethod="GetEmployees"
UpdateMethod="UpdateEmployee" TypeName="Model.EmployeeDB"
DataObjectTypeName="Model.EmployeeDetails"
onupdating="sourceEmployees_Updating">
<UpdateParameters>
<asp:Parameter Name="id" Type="int32" />
</UpdateParameters>
</asp:ObjectDataSource>
protected void sourceEmployees_Updating(object sender, ObjectDataSourceMethodEventArgs e)
{
e.InputParameters["id"] = e.InputParameters["EmployeeID"];
e.InputParameters.Remove("EmployeeID");
}
執行新增和刪除操作時一樣,差別在於處理事件要相應變化,也可以用類似的方法添加額外的參數並在事件中為它賦值,甚至在事件中還可以將 ObjectDataSource 指向同一個類中不同的更新方法:
sourceEmployees.UpdateMethod = "UpdateEmployeeStrict";
2. 在新增操作中處理標識值
下面這個示例,上部分是 DetailsView 允許用戶添加記錄,GridView 顯示當前存在的所有記錄,並允許用戶刪除它們。
<asp:DetailsView ID="detailsInsertEmployee" runat="server" DataSourceID="sourceEmployees"
DefaultMode="Insert" AutoGenerateInsertButton="true">
</asp:DetailsView>
<asp:Label ID="lblConfirmation" runat="server" EnableViewState="false"></asp:Label><br />
<br />
<asp:GridView ID="gridEmployeeList" runat="server" DataSourceID="sourceEmployees"
AutoGenerateDeleteButton="true">
</asp:GridView>
<asp:ObjectDataSource ID="sourceEmployees" runat="server" TypeName="Model.EmployeeDB"
SelectMethod="GetEmployees" DeleteMethod="DeleteEmployee" InsertMethod="InsertEmployee"
DataObjectTypeName="Model.EmployeeDetails"
oninserted="sourceEmployees_Inserted">
<InsertParameters>
<asp:Parameter Name="EmployeeID" Type="Int32" Direction="ReturnValue" />
</InsertParameters>
</asp:ObjectDataSource>
出於其他原因你可能希望使用標識值,如顯示確認信息等。因此在 ObjectDataSource 中定義一個參數來接收Insert方法的返回值。接着可以響應 Inserted 事件來獲取參數:
protected void sourceEmployees_Inserted(object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.Exception!=null)
{
lblConfirmation.Text = "Inserted record " + e.ReturnValue;
}
else
{
lblConfirmation.Text = e.Exception.Message;
e.ExceptionHandled = true;
}
}
效果:
使用特性標識數據訪問類
使用智能標簽可以啟動數據源配置向導,向導會引導你完成一系列步驟,提示你選取數據訪問類並選擇用於 增刪改查 的方法。
以下技巧可以讓你的數據庫組件能和數據源配置向導這類工具很好的工作。(但不是必須的)
using System.ComponentModel; // 引入命名空間
// 將某一類型標識為適合綁定到 System.Web.UI.WebControls.ObjectDataSource 對象的對象。
[DataObject] // 添加特性
public class EmployeeDB
如果在向導的對話框啟用了只顯示數據組件復選框,就會只看見使用了 DataObject 特性的類,這是快速訪問解決方案中數據訪問類的好方法。
[DataObjectMethod(DataObjectMethodType.Select,true)]
public List<EmployeeDetails> GetEmployees()
DataObjectMethod 特性標識了該方法所執行的操作類型以及該方法是否是默認的數據方法。DataObjectMethodType 枚舉標識該方法所執行的數據操作類型(增刪改查及填充 DataSet)。
第2個布爾參數表明同一操作類型的多個方法中的默認方法。(比如有多個 select 方法,默認方法應該為未經過濾的所有記錄的集合)
數據源控件的限制
整體而言,對於 ASP.NET 開發者來說,數據綁定是一項非凡的創新,不過你還是會碰到綁定所不能及的狀況,甚至要完全放棄綁定。
問題
一個下拉表顯示城市,一個網格顯示對應的雇員。你可能希望在下拉列表中添加兩個選項(選擇城市、選擇所有城市),怎么做呢?
數據綁定的一個弱點就是你從來都不能顯式的處理或創建用戶綁定控件的數據對象。它產生的后果是,你沒有機會添加額外的項。
這里的兩個難點在於:1. 如何添加新增項? 2. 新增項被選中時如何替換自動產生的邏輯?
添加其他項
這個問題有幾個解決辦法,但沒有一個是完美的。
- 重寫查詢:select '(Choose a City)' AS City union select distinct city from Employees ;采用這種方法的問題在於你不得不把表現層的細節問題加入到數據層。
- 編程解決:在正常操作中,數據源控件在相關聯的控件需要數據或者更新提交完成時被自動調用。很少有人知道,其實還可以通過編程調用 Select()、Insert()、Delete()、Update()這些方法來接管數據源控件,此時將完全由你來決定返回的結果。為了使這些成為現實,首先要刪除 DropDownList.DataSourceID屬性,而在頁面第一次加載時綁定控件。
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
ddlCities.DataSource = sourceEmployees.Select(DataSourceSelectArguments.Empty);
ddlCities.DataBind();
ddlCities.Items.Insert(0, "(Choose a City)");
ddlCities.Items.Insert(1, "(All City)");
ddlCities.SelectedIndex = 0;
}
}
使用 SqlDataSource 處理其他選項
下一個挑戰是如何阻止前面兩個項目的單擊事件。可以通過數據源的 Selecting 事件來實現:
protected void SqlDataSource1_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
{
if (e.Command.Parameters["@city"].Value == "(Choose a City)")
{
e.Cancel = true;
}
else
{
e.Command.CommandText = "select * from Employees";
}
}
這是一種硬編碼的暴力解決方式,很難看。
使用 ObjectDataSource 處理其他選項
ObjectDataSource 可以把這個問題解決的好一點,因為它可以講命令重定向到其他方法:
protected void sourceEmployees_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
{
if (e.InputParameters["city"].ToString() == "(Choose a City)")
{
e.Cancel = true;
}
else if(e.InputParameters["city"].ToString() == "(All City)")
{
sourceEmployees.SelectMethod = "GetAllEmployees";
// 必須移除參數,ObjectDataSource 是根據參數去匹配數據組件中的方法的
e.InputParameters.Remove("city");
}
}