本節涉及的內容如下:
一,資料繫結方法的來源以及在低層上的實現。
二,資料繫結方法的執行效率排序。
<%#Container.DataItem%>
<%#GetDataItem()%>
<%#Eval("欄位名")%>
<%#DataBinder.Eval(Container.DataItem,"欄位名")%>
<%#((DataRowView)Container.DataItem)["欄位名"] %>
<%#((Type)Container.DataItem).成員 %>
<%#((Type)GetDataItem()).成員 %>
上面七種綁定形式以及它們的變幻形式都用過嗎?效能怎麼排序?
複習一下:第一節我們主要談了資料繫結運算式的各種形式,在ASP.NET頁面中出現的位置,以及我們常綁定到與資料庫有關的DataView,DataTable,DataSet 等資料來源的資料繫結運算式的各種形式。
你有沒有對Eval方法和DataBinder.Eval方法好奇過?
在.NET2.0中我們經常用Eval方法在Repeater,DataList,GridView等迴圈控制項中綁定資料,Eval方法和DataBinder.Eval方法在低層是怎麼實現的?它們到底有什麼千絲萬縷的關係?
一,來源、實現。
我們常用的Eval方法其實是Page類的一個靜態單向唯讀方法,而且它是一個受保護的方法。實際上Page類的Eval方法是繼承自TemplateControl類的。TemplateControl 類是一個抽象類別,它為Page 類和 UserControl 類提供通用屬性和方法。我們先來看一下繼承家譜:
System.Object
System.Web.UI.Control
System.Web.UI.TemplateControl
System.Web.UI.Page
System.Web.UI.UserControl
Eval方法就是TemplateControl類的方法,它有兩種形式:
| 名稱 |
說明 |
| TemplateControl.Eval (String) |
計算資料繫結運算式。 |
| TemplateControl.Eval (String, String) |
使用用於顯示結果的指定格式字串計算資料繫結運算式。 |
事實上TemplateControl類還提供了XPath方法和XPathSelect方法供Page類和UserControl繼承。這2個方法是和XML資料來源有關的Binder 方法。
如果細心的你查看TemplateControl類的基類Control類,你就會發現其實Control類並沒有提供Eval,XPath,XPathSelect等方法。所以Eval,XPath等方法最終是在TemplateControl類中實現的。
現在,終於找到了Eval,XPath等資料繫結方法的來源了。
Eval,XPath等方法是。NET 2.0新增的方法。在。NET 1.1時代我們經常用的是DateBinder.Eval方法。形如:
<%#DataBind.Eval(Container.DataItem,"欄位名") %>
<%#DataBind.Eval(Container.DataItem,"欄位名","{0:c}") %>
Eval的出現其實就是為了簡化DataBinder.Eval方法的寫法從而代替它。
在ASP.NET 2.0中及以上,當我們調用Eval時,Eval 方法會使用GetDataItem方法調用DataBinder.Eval方法計算運算式的值。要想理解這句話,就算查邊MSDN也一頭霧水,除非我們知道Eval方法的原始碼,否則根本找不到蛛絲馬跡。這裡就要用到反射了。我們通過反射獲得了Eval方法的原始碼:
protected internal object Eval(string expression)
{
this.CheckPageExists();
return DataBinder.Eval(this.Page.GetDataItem(), expression);
}
終於見到GetDataItem()方法了,其實它就是Page類的一個方法,也是。NET 2.0新增一個方法。GetDataItem()方法的作用是為了獲得Container.DataItem,它是。NET 2.0中用來代替Container.DataItem的,如果你曾經用Repeater和DataList等綁定過數組或者ArrayList等,你就會發現<%#GetDataItem()%>和<%#Container.DataItem%>等價。同時,可以肯定:Eval方法在低層上確實是調用DataBinder.Eval方法實現資料繫結的。其中“this.CheckPageExists();”
是檢查調用的時候有沒有Page對象的,如果沒有則會拋出一個異常。
要弄清Eval是怎麼工作的,GetDataItem()方法的低層實現我們也要用反射來擷取:
public object GetDataItem()
{
if ((this._dataBindingContext == null) || (this._dataBindingContext.Count == 0))
{
throw new InvalidOperationException(SR.GetString("Page_MissingDataBindingContext"));
}
return this._dataBindingContext.Peek();
}
我們從GatDataItem()方法中看到“return this._dataBindingContext.Peek();”很快就猜想_dataBindingContext是不是一個堆棧呢?事實它就是一個堆棧!通過反射查看原始碼我們得出:_dataBindingContext是一個Stack類型對象。所以它有Peek方法。“return this._dataBindingContext.Peek(); ”正是把堆棧頂部的元素返回。而if語句是用來判斷這個堆棧是否已經存在或者是否已經有元素存在,如果if不成立,就會拋出一個異常。
從上面的分析我們知道:_dataBindingContext堆棧的作用是通過GetDataItem()方法這個橋樑向Eval方法提供Container.DateItem.用逆向思維來理解上面這句話:Eval方法可以自動計算出Container.DataItem,原因就是從dataBindingContext堆棧來擷取Container.DataItem;這也就為什麼Eval方法能夠知道形如<%#Eval"欄位名"%>中欄位名隸屬於哪個資料項目的屬性的原因;同時我們也知道。NET 2.0中的Eval在本質上的實現並沒有拋棄Container.DataItem,而Container.DataItem在2.0時代也沒有消失。
那麼_dataBindingContext這個儲存Container.DataItem的堆棧是怎麼建立的呢?
我們很快就想到每次繫結控制項時候最後那條語句是什麼:this.控制項ID.DataBind();對就是DataBind()方法,DataBind()方法還有一個重載:DataBind(bool raiseOnDataBinding)。為_dataBindingContext這個堆棧壓入元素和彈出元素的方法正是用DataBind(bool flag)這個重載方法實現的。
DataBind(bool raiseOnDataBinding)在低層的實現:
protected virtual void DataBind(bool raiseOnDataBinding)
{
bool flag1 = false;//這個標誌的用處在上下文中很容易推出來,如果有DataItem壓棧,則在後面出棧。
if (this.IsBindingContainer)//判斷控制項是不是資料繫結容器,實際上就是判斷控制項類是不是實現了INamingContainer
{
bool flag2;
object obj1 = DataBinder.GetDataItem(this, out flag2);//這個方法是判斷控制項是不是有DataItem屬性,並把它取出來。
if (flag2 && (this.Page != null))//如果控制項有DataItem
{
this.Page.PushDataBindingContext(obj1);//把DataItem壓棧,PushDataBindingContext就是調用_dataBindingContext的Push方法
flag1 = true;
}
}
try
{
if (raiseOnDataBinding)//這裡是判斷是不是觸發DataBinding事件的。
{
this.OnDataBinding(EventArgs.Empty);
}
this.DataBindChildren();//對子控制項進行資料繫結,如果這個控制項有DataItem,則上面會將DataItem壓入棧頂,這樣,在子控制項裡面調用Eval或者GetDataItem方法,就會把剛剛壓進去的DataItem給取出來。
}
finally
{
if (flag1)//如果剛才有壓棧,則現在彈出來。
{
this.Page.PopDataBindingContext();//PopDataBindingContext就是調用_dataBindingContext的Pop方法
}
}
}
當我們執行到this.控制項ID.DataBind();時候。在低層上就會調用這個重載的方法來準備包含DataItem的_DatBindingContext堆棧。
上面的代碼中提到了DataBinding事件,那麼它一般什麼時候被觸發呢?
1,如果用編程方式,那麼在我們調用DataBind()方法時候自動觸發DataBinding事件。
2,如果我們用資料來源控制項(例如SqlDataSource等),當把控制項綁定到資料來源控制項時候,這個事件就會自動觸發。
一般資料繫結運算式常常放在模板中迴圈顯示資料,例如Repeater和DataList等的模板。那麼下面這個知識點應該知道:Repeater,DataList,FormView等控制項必須使用模板,如果不使用模板,這些控制項將無法顯示資料。而GridView,DetailsView,Menu等控制項也支援模板,但顯示資料時不是必須的。而TreeView控制項不支援模板。
注意:一般情況下,資料繫結運算式不會自動計算它的值,除非它所在的頁或者控制項顯示調用DataBind()方法。DataBind()方法能夠將資料來源綁定到被調用的伺服器控制項及其所有子控制項,同時分析並計算資料繫結運算式的值。
終於寫的有點眉目了,好累!我們該回頭看看Eval方法調用的靜態DataBinder.Eval方法在低層的實現了。我把DataBinder類的原始碼作為附近提供下載。
二,執行效率
從“一”講述的低層實現。我們很容易來排序下面資料繫結運算式的執行效率
<%#Container.DataItem%>
<%#GetDataItem()%>
<%#Eval("欄位名")%>
<%#DataBinder.Eval(Container.DataItem,"欄位名")%>
<%#((DataRowView)Container.DataItem)["欄位名"] %>
<%#((Type)Container.DataItem).成員 %>
<%#((Type)GetDataItem()).成員 %>
1,效率最高應該是:
<%#((Type)Container.DataItem).成員 %>
<%#Container.DataItem%>
<%#((DataRowView)Container.DataItem)["欄位名"] %>
2,效率排第二的是:
<%#((Type)GetDataItem()).成員 %>
<%#GetDataItem()%>
3,效率最低的是:
<%#Eval("欄位名")%>
<%#DataBinder.Eval(Container.DataItem,"欄位名")%>
其實按上面的排序有失公允,原因是這七種資料表達綁定形式運用的場合不是完全相同的。
使用場合大概如下:
1,
|<%#Eval("欄位名")%>
<%#DataBinder.Eval(Container.DataItem,"欄位名")%>
它們的使用場合最廣,資料來源可以為與資料庫有關的DataSet,DataTable,DataView.也可以為普通集合(例如:數組,ArrayList,HashTable等)和泛行集合(例如:List<T>,Dictionary<Tkey,Tvalue>等)。
註:它們2個永遠可以相互替換,至少目前是這樣,凡是可以用Eval方法的地方,就可以用DataBinder.Eval方法替換。從低層實現上,Eval比DataBinder.Eval方法效率稍低,原因是Eval方法對了調用GetDataItem()方法這一步。但最終都是通過DataBinder.Eval方法利用反射技術根據名稱尋找屬性,從而計算出運算式的值,所以非常影響效能。
2,
<%#((DataRowView)Container.DataItem)["欄位名"] %>
它只能使用在資料來源為與資料庫有關的Dataset,DatTable,DataView.這些資料來源都實現了IListSource介面。其實從低層實現本質上來看,它和<%#((Type)Container.DataItem)。成員 %>類似。
3,
<%#Container.DataItem%>
<%#GetDataItem()%>
<%#((Type)Container.DataItem).成員 %>
<%#((Type)GetDataItem()).成員 %>
這幾種形式估計大家最不常用。它們一般只使用與普通集合(例如:數組,ArrayList,HashTable)和泛行集合(例如:List<T>,Dictionary<Tkey,Tvalue>)。其實本質上就是實現了IList,ICollection,IEnumerable,IDictionary等以及這些介面對應的泛行介面的集合。IList介面和IDictionary介面的區別是,一個只有值,而另一個是鍵/值對,對應泛行形式也是這樣。而Array就對用List<T>,而HashTable就對應Dictionary<Tkey,Tvalue>.
原文地址:http://dotnet.chinaitlab.com/List_238.html