Step 1: Updating a Product’s ProductName and UnitPrice Fields
在上一個指南中邊界數(shù)據(jù)必須包括product所有非只讀字段。如果我們從GridView移除掉一個字段,例如QuantityPerUnit,結(jié)果我們更新時,數(shù)據(jù)控件將不能給ObjectDataSource 的 QuantityPerUnit 這個UpdateParameters賦值,ObjectDataSource傳給UpdateProduct業(yè)務(wù)邏輯層方法一個空值,將把數(shù)據(jù)庫這行數(shù)據(jù)里的QuantityPerUnit列變?yōu)镹ULL。如果去除的是必添字段比如ProductName,結(jié)果引發(fā)異常“Column ‘ProductName‘ does not allow nulls”,所以O(shè)bjectDataSourc的UpdateParameters集合與相應(yīng)的方法參數(shù)匹配。
如果我們做的數(shù)據(jù)web控件允許用戶僅僅更新這些字段的子集。我們需要編程在ObjectDataSource的Updating事件里設(shè)置丟失的ObjectDataSource 的UpdateParameter的值,或者建立一個僅包含這些字段的業(yè)務(wù)邏輯層的方法,下面具體解釋如果操作
現(xiàn)在,建立一個編輯模式僅包含ProductName and UnitPrice字段的GridView,這個GridView編輯界面只能讓用戶修改這兩個字段。因為編輯界面只提供這product的部分字段值,且ObjectDataSource調(diào)用了現(xiàn)有的業(yè)務(wù)邏輯層的UpDateProduct方法,我們需要在UpDating事件處理函數(shù)里處理那些缺失的參數(shù),或者建立一個新的僅包括這些參數(shù)的業(yè)務(wù)邏輯層UpDateProduct方法。本指南使用后一種方法。下面我們重載UpdateProduct方法,他只包括3個參數(shù)productName, unitPrice, and productID,代碼如下
[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
product.ProductName = productName;
if (unitPrice == null) product.SetUnitPriceNull(); else product.UnitPrice = unitPrice.Value;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
同原來的UpdateProduct一樣,先通過輸入的ProductId檢測表中是否有要更新的數(shù)據(jù),如果沒有,返回false,表明更新失敗。否則通過TableAdpater的Update()方法更新已存在的數(shù)據(jù)的ProductName和UnitPrice字段。
因為ProductsBLL改變了,我們建立一個簡單的GridView,打開EditInsertDelete文件夾下的DataModificationEvents.aspx,添加一個GridView,和ObjectDataSource,配置ObejctDataSource的Select影射到ProductsBLL的GetProducts,Update影射到UpdateProduct重載的方法如Figure2所示,
我們的例子只是說明如果編輯數(shù)據(jù),不包括插入和刪除數(shù)據(jù),所以O(shè)bjectDataSource的Insert方法和Delete方法不影射到任何ProductsBLL的任何方法,在向?qū)Ю镌贗nsert和DELETE標(biāo)簽里選擇(None)。
完成向?qū)Ш螅ㄟ^GridView的智能標(biāo)記把Enable Editing打勾。
完成了建立數(shù)據(jù)源的向?qū)В阉壎ǖ紾ridView,Visual Studio會為它們自動建立代碼,在Source view可以看到ObjectDataSource的代碼如下。
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
TypeName="ProductsBLL" UpdateMethod="UpdateProduct">
<UpdateParameters>
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="unitPrice" Type="Decimal" />
<asp:Parameter Name="productID" Type="Int32" />
</UpdateParameters>
</asp:ObjectDataSource>
因為沒有影射ObjectDataSource的Insert()和Delete()方法,所以這里沒有InsertParameter和DeleteParameters節(jié)。Update()方法影射到重載的UpdateProduct方法,所以UpdateParameters節(jié)僅有3個Parameter實例。
注意ObjectDatasource的OldValuesParameterFormatString屬性被聲明為original{0},這是配置數(shù)據(jù)源時Visual Studio自動生成的。然而,我們業(yè)務(wù)邏輯方法不需要原來的ProductId之所以把它從ObjectDataSource的代碼去掉。
注意:如果你簡單的在設(shè)計視圖里的屬性窗口去掉OldValuesParamerFormatString屬性值,在代碼里這個屬性依舊存在,只是被設(shè)為空字符串,應(yīng)該直接在代碼里刪除它,或者在屬性窗口把它設(shè)想為默認(rèn)值{0}。
現(xiàn)在ObjectDataSource只有帶著product’s name, price, 和ID 的UpdateParameters,
Visual Studio 也把product的字段添加成GridView的BoundField or CheckBoxField。
用戶點Update button ,GridVeiw枚舉所有的非只讀字段,把它們的用戶輸入賦給ObjectDataSource的UpdateParameters集合相應(yīng)的參數(shù),如果沒有相應(yīng)的參數(shù),GridView會自己添加一個,因此如果GridView包含product所有字段的BoundFields and CheckBoxFields,ObjectDataSource要使用所有的參數(shù)來調(diào)用重載的UpdateProduct,盡管只需要3個參數(shù)。同樣的GridView中綁定的所用product非只讀字段不符合重載的UpdateProduct方法要求的輸入?yún)?shù),所以更新時會引發(fā)異常
為了確保ObjectDataSource調(diào)用UpdateProduct方法時獲得正確的參數(shù),我們需要限制GridView的可編輯字段只包括Productname 和UnitPrice。我們可以刪除其余字段,或者把其余字段設(shè)為只讀。本指南讓我們簡單的移除除了ProductName和UnitPrice外的BoundFields,GridView代碼聲明如下。
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID"
DataSourceID="ObjectDataSource1" EnableViewState="False">
<Columns>
<asp:CommandField ShowEditButton="True" />
<asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" />
</Columns>
</asp:GridView>
即使重載的UpdateProdcut要求兩個輸入?yún)?shù),我們只提供了連個,這是因為ProductId輸入?yún)?shù)是作為主鍵傳送的,因為ProductId是GridView的DatakeyName屬性。
我們的GridView,通過UpdateProduct的重載函數(shù),允許用戶只編輯name和price。
Improving the UnitPrice Formatting
這個GridView的例子,UnitPrice字段是么有格式要求的。Price字段單位是金額這樣的顯示效果是不好的。應(yīng)該顯示為金額符號加上4位小數(shù)。在非編輯狀態(tài)下修改格式,簡單的把UnitPrice BoundFields的DataFormatString屬性設(shè)為{0:c}且HtmlEncode屬性設(shè)為false。
隨著這次的修改,非編輯狀態(tài)下的price顯示為金額格式。編輯模式依舊沒有顯示為金額格式。
DataFormatString屬性可以應(yīng)用于編輯界面。把Boundfield的ApplyformatInEditMode屬性設(shè)為true。
現(xiàn)在UnitPrice在編輯狀態(tài)下也顯示為金額格式。
然而,現(xiàn)在更新product,比如用戶輸入“$19.00”,會引發(fā)一個FormatException異常。因為GridView試圖把用戶輸入傳入ObjectDatasource的UpdateParameters集合。輸入的其實是一個字符串”$19.00”,程序需要把它轉(zhuǎn)換為所需的decimal。為了補救這些,我們需要建立一個GridView的RowUpdating事件處理程序,在那里把用戶輸入轉(zhuǎn)為正確的數(shù)據(jù)。
GridView’s RowUpdating事件接受兩個參數(shù),一個對象是GridViewUpDateEventArgs類型。它有一個NewValues字典類型的屬性。在這里就是用戶提供給ObjectDataSource的UpdateParameters集合的值。我們可以重寫已經(jīng)存在的UnitPrice值。代碼如下。
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
if (e.NewValues["UnitPrice"] != null)
e.NewValues["UnitPrice"] = decimal.Parse(e.NewValues["UnitPrice"].ToString(), System.Globalization.NumberStyles.Currency);
}
如果用戶輸入的UnitPrice值(比如“$19.00”),這個之會被Decimal.Parase重寫為decimal值。.
Step 2: Prohibiting NULL UnitPrices
數(shù)據(jù)庫里允許Produc表的UnitPrice列允許輸入控制。但是程序可能不允許這樣,因此,如果用戶輸入一個空值,需要顯示一個錯信息來提示他。
GridViewUpdateEventArgs對象被傳入GridView的Rowupdateing事件處理函數(shù)。它有一個Cancel屬性。如果設(shè)為true,就結(jié)束更新?,F(xiàn)在讓我們修改RowUpdating事件處理函數(shù)。如果用戶輸入控制,就把這個對象Cancel設(shè)為true,并顯示一個錯誤消息。
添加一個Label 控件,命名為MustProvideUnitPriceMessage。如果用戶有錯誤的輸入,則顯示出來“You must provide a price for the product”,再在Style.cs建立一個新的CSS類定義如下
.Warning
{
color: Red;
font-style: italic;
font-weight: bold;
font-size: x-large;
}
最后設(shè)定這個Label的CssClass屬性為Warning。具體顯示如下圖。
默認(rèn)的這個label應(yīng)該是隱藏的。于是在Page_Load事件處理函數(shù)設(shè)定它的Visible屬性為false
protected void Page_Load(object sender, EventArgs e)
{
MustProvideUnitPriceMessage.Visible = false;
}
如果用戶更新Product但沒有輸入UnitPice,需要中斷這個處理。并顯示錯誤信息。代碼如下。
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
if (e.NewValues["UnitPrice"] != null)
e.NewValues["UnitPrice"] = decimal.Parse(e.NewValues["UnitPrice"].ToString(), System.Globalization.NumberStyles.Currency);
else
{
// Show the Label
MustProvideUnitPriceMessage.Visible = true;
// Cancel the update
e.Cancel = true;
}
}
Figure 13: A User Cannot Leave UnitPrice Blank
目前為止,解釋了如何在GridView的RowUpdating事件處理函數(shù)編程改變ObjectDataSource的UpdateParameters參數(shù)。還有如何作廢更新的處理過程。這些概念也適用于DetailView和FormView。也適用于它們的Insert和delete處理過程。
這些就是ObjectDataSource級別的事件處理函數(shù)主要任務(wù)。這些事件在底層對象處理數(shù)據(jù)之前被觸發(fā)。它提供最后的機會去修改輸入?yún)?shù)和中斷處理流程。這些事件處理函數(shù)都有一個ObjectDataSourceMethodEventArgs類型的輸入?yún)?shù),這個參數(shù)有兩個屬性十分重要。
• Cancel,如果設(shè)為true,則中止操作。
• InputParameters,它包括了所有的輸入?yún)?shù)集合,
UpdateParameters 還有DeleteParameters,這些類型的對象依賴于何種事件被觸發(fā)(Instering,Updaing,或者Deleting),會被傳入相應(yīng)的事件處理函數(shù)。
以上解釋了在ObjectDataSource級別上參數(shù)如何處理。下面在頁面上加一個DetailsView控件,可以讓用戶把新的product添加到數(shù)據(jù)庫。為了和以上說明一致,這里只允許用戶輸入新的Productname,和UnitPrice字段。默認(rèn)下DetailsView 插入界面沒有提供的字段會被把空值插入數(shù)據(jù)庫,我們需要在ObjectDataSource的Inserting事件處理函數(shù)防止這些默認(rèn)參數(shù)值。下面看看如何做。
Step 3: Providing an Interface to Add New Products
從工具欄上拖一個DetailsView到GridView上面,清除Height和Width屬性,綁定到頁面已有的ObjectDataSource,這會把Product的字段添加為BoundField或者CheckBoxField。為了能過讓Details添加數(shù)據(jù),需要在智能標(biāo)記上選擇Enable Inserting選項。然而,因為前面ObjectDataSource的insert()方法沒有影射到任何ProductsBLL類的方法。
現(xiàn)在選擇ObjectDataSource職能標(biāo)記配置數(shù)據(jù)源,進入向?qū)?,先把ProductsBLL綁定到ObjectDataSource,接著影射ObjectDataSource的Insert()方法到ProductsBLL的AddProduct,因為DataObjectMethodAttribute屬性,所以這些方法仍然是默認(rèn)的方法。
配置萬Insert,接著依然把DELETE標(biāo)簽的下拉列表選為(None).
通過這些改變,ObjectDataSource代碼會包括InsertParameter集合,如下所示
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetProducts"
TypeName="ProductsBLL" UpdateMethod="UpdateProduct" OnUpdating="ObjectDataSource1_Updating" InsertMethod="AddProduct" OldValuesParameterFormatString="original_{0}">
<UpdateParameters>
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="unitPrice" Type="Decimal" />
<asp:Parameter Name="productID" Type="Int32" />
</UpdateParameters>
<InsertParameters>
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="supplierID" Type="Int32" />
<asp:Parameter Name="categoryID" Type="Int32" />
<asp:Parameter Name="quantityPerUnit" Type="String" />
<asp:Parameter Name="unitPrice" Type="Decimal" />
<asp:Parameter Name="unitsInStock" Type="Int16" />
<asp:Parameter Name="unitsOnOrder" Type="Int16" />
<asp:Parameter Name="reorderLevel" Type="Int16" />
<asp:Parameter Name="discontinued" Type="Boolean" />
</InsertParameters>
</asp:ObjectDataSource>
再次運行向?qū)?,會添加OldValuesParameterFormatString屬性,花一些時間去除它,或把它設(shè)為默認(rèn)值{0}。
因為ObjectDataSource提供了插入數(shù)據(jù)的能力,DetailsView的智能標(biāo)記現(xiàn)在可以讓你選擇Enable Inserting checkbox,選擇它,現(xiàn)在DetailsView僅有兩個BoundFields(ProductName、UnitPrice)和一個CommandField,代碼如下
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataKeyNames="ProductID"
DataSourceID="ObjectDataSource1" EnableViewState="False">
<Fields>
<asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" />
<asp:CommandField ShowInsertButton="True" />
</Fields>
</asp:DetailsView>
Figure16顯示了游覽本頁的效果。正如你看到的DetailsView列出了第一個product的名字(Chai),然而我們需要一個插入界面,可以讓用戶快速插入數(shù)據(jù)到數(shù)據(jù)庫中。
為了讓DetailsView顯示為插入模式,我們需要設(shè)定DefaultMode屬性為Inserting,DetailsView會呈現(xiàn)為插入模式,如Figure17顯示的那樣。
當(dāng)用輸入了Product的name和price(比如“Acme Water” 和1.99 ),點擊Insert button,頁面回送服務(wù)器一個插入數(shù)據(jù)的流程開始了,最后一個新的產(chǎn)品插入的數(shù)據(jù)庫。DetailsView負(fù)責(zé)插入,GridView在插入后自動綁定到新的數(shù)據(jù)。如Figure18顯示的那樣
這里GridView并沒有顯示所有的product字段(CategoryID,SupplierID,QuantityPerUnit 等等),你可以通過以下步驟看到這些值
1. 進入Visual?。樱簦酰洌椋锏模樱澹颍觯澹颉。牛穑欤铮颍澹?。
2. 展開NORTHWND.MDF節(jié)點
3. 右擊Product數(shù)據(jù)庫的表節(jié)點
4. 選擇顯示表數(shù)據(jù)
如Figure19顯示的那樣,這會列出Product表所有記錄。我們新輸入的記錄ProductID, ProductName, and UnitPrice被賦予NULL值
某些情況下我們需要提供默認(rèn)值代替NULL,例如:可能數(shù)據(jù)庫會不允許空值等等。我們可以編程設(shè)定DetailsView的InputParameters集合的值,這些操作可以在DetailsView的ItemInerting事件,或者ObjectDataSource的Inserting事件處理函數(shù)里做。前面我們已經(jīng)看到用Data Web Control 提交之前的事件處理這一切,現(xiàn)在我們在ObjectDataSource的事件里處理它
Step 4: Assigning Values to the CategoryID and SupplierID Parameters
在這個指南中假設(shè)我們的程序添加新的product時。它的CategroyID和SupplierID默認(rèn)值為1
從前面的指南我們知道ObjectDataSource提交數(shù)據(jù)時有一對事件要被觸發(fā)。當(dāng)Insert()被調(diào)用,ObjectDataSource首先觸發(fā)Inserting事件,接著調(diào)用Insert()方法影射的方法,最后觸發(fā)Inserted事件。Inserting事件處理函數(shù)為我們提供了最后的機會來修改輸入?yún)?shù)或者中斷處理。
注意:實際應(yīng)用程序應(yīng)該讓用戶可以詳細(xì)指定category and supplier的值而不是簡簡單單的設(shè)為1,這里只是解釋如何編程設(shè)定輸入值。
添加ObjectDataSource的Inserting事件,注意事件處理函數(shù)有兩個參數(shù),第二個是ObjectDataSourceMethodEventArgs類型的對象。可以通過它訪問輸入?yún)?shù)集合。或者中斷處理過程。
protected void ObjectDataSource1_Inserting(object sender, ObjectDataSourceMethodEventArgs e)
{
}
InputParameters屬性包括了ObjectDataSource的InsertParameters集合,簡單使用e.InputParameters["paramName"] = value ,就可以把e CategoryID and SupplierID值設(shè)為1,代碼如下。
protected void ObjectDataSource1_Inserting(object sender, ObjectDataSourceMethodEventArgs e)
{
e.InputParameters["CategoryID"] = 1;
e.InputParameters["SupplierID"] = 1;
}
先在我們添加一個新的Product,如Figure20看到的新的記錄的CategoryID and SupplierID值為1