这一节继续来谈NET中的数据绑定表达式
本节涉及的内容如下
一数据绑定方法的来源以及在低层上的实现
二数据绑定方法的执行效率排序
<%#ContainerDataItem%>
<%#GetDataItem()%>
<%#Eval(字段名)%>
<%#DataBinderEval(ContainerDataItem字段名)%>
<%#((DataRowView)ContainerDataItem)[字段名] %>
<%#((Type)ContainerDataItem)成员 %>
<%#((Type)GetDataItem())成员 %>
上面七种绑定形式以及它们的变幻形式都用过吗?性能怎么排序?
复习一下第一节我们主要谈了数据绑定表达式的各种形式在ASPNET页面中出现的位置以及我们常绑定到与数据库有关的DataViewDataTableDataSet 等数据源的数据绑定表达式的各种形式
你有没有对Eval方法和DataBinderEval方法好奇过?
在NET中我们经常用Eval方法在RepeaterDataListGridView等循环控件中绑定数据Eval方法和DataBinderEval方法在低层是怎么实现的?它们到底有什么千丝万缕的关系?
一来源实现
我们常用的Eval方法其实是Page类的一个静态单向只读方法而且它是一个受保护的方法实际上Page类的Eval方法是继承自TemplateControl类的TemplateControl 类是一个抽象类它为Page 类和 UserControl 类提供通用属性和方法我们先来看一下继承家谱
SystemObject
SystemWebUIControl
SystemWebUITemplateControl
SystemWebUIPage
SystemWebUIUserControl
Eval方法就是TemplateControl类的方法它有两种形式
名称 说明TemplateControlEval (String) 计算数据绑定表达式TemplateControlEval (String String)使用用于显示结果的指定格式字符串计算数据绑定表达式
事实上TemplateControl类还提供了XPath方法和XPathSelect方法供Page类和UserControl继承这个方法是和XML数据源有关的绑定方法
如果细心的你查看TemplateControl类的基类Control类你就会发现其实Control类并没有提供EvalXPathXPathSelect等方法所以EvalXPath等方法最终是在TemplateControl类中实现的
现在终于找到了EvalXPath等数据绑定方法的来源了
EvalXPath等方法是NET 新增的方法在NET 时代我们经常用的是DateBinderEval方法形如
<%#DataBindEval(ContainerDataItem字段名) %>
<%#DataBindEval(ContainerDataItem字段名{c}) %>
Eval的出现其实就是为了简化DataBinderEval方法的写法从而代替它
在ASPNET 中及以上当我们调用Eval时Eval 方法会使用GetDataItem方法调用DataBinderEval方法计算表达式的值要想理解这句话就算查边MSDN也一头雾水除非我们知道Eval方法的源代码否则根本找不到蛛丝马迹这里就要用到反射了我们通过反射获得了Eval方法的源代码
protected internal object Eval(string expression)
{
thisCheckPageExists()
return DataBinderEval(thisPageGetDataItem() expression)
}
终于见到GetDataItem()方法了其实它就是Page类的一个方法也是NET 新增一个方法GetDataItem()方法的作用是为了获得ContainerDataItem它是NET 中用来代替ContainerDataItem的如果你曾经用Repeater和DataList等绑定过数组或者ArrayList等你就会发现<%#GetDataItem()%>和<%#ContainerDataItem%>等价同时可以肯定Eval方法在低层上确实是调用DataBinderEval方法实现数据绑定的其中thisCheckPageExists() 是检查调用的时候有没有Page对象的如果没有则会抛出一个异常
要弄清Eval是怎么工作的GetDataItem()方法的低层实现我们也要用反射来获取
public object GetDataItem()
{
if ((this_dataBindingContext == null) || (this_dataBindingContextCount == ))
{
throw new InvalidOperationException(SRGetString(Page_MissingDataBindingContext))
}
return this_dataBindingContextPeek()
}
我们从GatDataItem()方法中看到return this_dataBindingContextPeek()很快就猜想_dataBindingContext是不是一个堆栈呢?事实它就是一个堆栈!通过反射查看源代码我们得出_dataBindingContext是一个Stack类型对象所以它有Peek方法return this_dataBindingContextPeek() 正是把堆栈顶部的元素返回而if语句是用来判断这个堆栈是否已经存在或者是否已经有元素存在如果if不成立就会抛出一个异常
从上面的分析我们知道_dataBindingContext堆栈的作用是通过GetDataItem()方法这个桥梁向Eval方法提供ContainerDateItem用逆向思维来理解上面这句话Eval方法可以自动计算出ContainerDataItem原因就是从dataBindingContext堆栈来获取ContainerDataItem这也就为什么Eval方法能够知道形如<%#Eval字段名%>中字段名隶属于哪个数据项的属性的原因同时我们也知道NET 中的Eval在本质上的实现并没有抛弃ContainerDataItem而ContainerDataItem在时代也没有消失
那么_dataBindingContext这个保存ContainerDataItem的堆栈是怎么建立的呢?
我们很快就想到每次绑定控件时候最后那条语句是什么this控件IDDataBind()对就是DataBind()方法DataBind()方法还有一个重载DataBind(bool raiseOnDataBinding)为_dataBindingContext这个堆栈压入元素和弹出元素的方法正是用DataBind(bool flag)这个重载方法实现的
DataBind(bool raiseOnDataBinding)在低层的实现
protected virtual void DataBind(bool raiseOnDataBinding)
{
bool flag = false;//这个标志的用处在上下文中很容易推出来如果有DataItem压栈则在后面出栈
if (thisIsBindingContainer)//判断控件是不是数据绑定容器实际上就是判断控件类是不是实现了INamingContainer
{
bool flag;
object obj = DataBinderGetDataItem(this out flag);//这个方法是判断控件是不是有DataItem属性并把它取出来
if (flag && (thisPage != null))//如果控件有DataItem
{
thisPagePushDataBindingContext(obj);//把DataItem压栈PushDataBindingContext就是调用_dataBindingContext的Push方法
flag = true;
}
}
try
{
if (raiseOnDataBinding)//这里是判断是不是触发DataBinding事件的
{
thisOnDataBinding(EventArgsEmpty);
}
thisDataBindChildren();//对子控件进行数据绑定如果这个控件有DataItem则上面会将DataItem压入栈顶这样在子控件里面调用Eval或者GetDataItem方法就会把刚刚压进去的DataItem给取出来
}
finally
{
if (flag)//如果刚才有压栈则现在弹出来
{
thisPagePopDataBindingContext();//PopDataBindingContext就是调用_dataBindingContext的Pop方法
}
}
}
当我们执行到this控件IDDataBind()时候在低层上就会调用这个重载的方法来准备包含DataItem的_DatBindingContext堆栈
上面的代码中提到了DataBinding事件那么它一般什么时候被触发呢?
1如果用编程方式那么在我们调用DataBind()方法时候自动触发DataBinding事件
2如果我们用数据源控件(例如SqlDataSource等)当把控件绑定到数据源控件时候这个事件就会自动触发
一般数据绑定表达式常常放在模板中循环显示数据例如Repeater和DataList等的模板那么下面这个知识点应该知道RepeaterDataListFormView等控件必须使用模板如果不使用模板这些控件将无法显示数据而GridViewDetailsViewMenu等控件也支持模板但显示数据时不是必须的而TreeView控件不支持模板
注意一般情况下数据绑定表达式不会自动计算它的值除非它所在的页或者控件显示调用DataBind()方法DataBind()方法能够将数据源绑定到被调用的服务器控件及其所有子控件同时分析并计算数据绑定表达式的值
终于写的有点眉目了好累!我们该回头看看Eval方法调用的静态DataBinderEval方法在低层的实现了我把DataBinder类的源代码作为附近提供下载
二执行效率
从一讲述的低层实现我们很容易来排序下面数据绑定表达式的执行效率
<%#ContainerDataItem%>
<%#GetDataItem()%>
<%#Eval(字段名)%>
<%#DataBinderEval(ContainerDataItem字段名)%>
<%#((DataRowView)ContainerDataItem)[字段名] %>
<%#((Type)ContainerDataItem)成员 %>
<%#((Type)GetDataItem())成员 %>
效率最高应该是
<%#((Type)ContainerDataItem)成员 %>
<%#ContainerDataItem%>
<%#((DataRowView)ContainerDataItem)[字段名] %>
效率排第二的是
<%#((Type)GetDataItem())成员 %>
<%#GetDataItem()%>
效率最低的是
<%#Eval(字段名)%>
<%#DataBinderEval(ContainerDataItem字段名)%>
其实按上面的排序有失公允原因是这七种数据表达绑定形式运用的场合不是完全相同的
使用场合大概如下
|<%#Eval(字段名)%>
<%#DataBinderEval(ContainerDataItem字段名)%>
它们的使用场合最广数据源可以为与数据库有关的DataSetDataTableDataView也可以为普通集合(例如数组ArrayListHashTable等)和泛行集合(例如List<T>Dictionary<TkeyTvalue>等)
注它们个永远可以相互替换至少目前是这样凡是可以用Eval方法的地方就可以用DataBinderEval方法替换从低层实现上Eval比DataBinderEval方法效率稍低原因是Eval方法对了调用GetDataItem()方法这一步但最终都是通过DataBinderEval方法利用反射技术根据名称查找属性从而计算出表达式的值所以非常影响性能
<%#((DataRowView)ContainerDataItem)[字段名] %>
它只能使用在数据源为与数据库有关的DatasetDatTableDataView这些数据源都实现了IListSource接口其实从低层实现本质上来看它和<%#((Type)ContainerDataItem)成员 %>类似
<%#ContainerDataItem%>
<%#GetDataItem()%>
<%#((Type)ContainerDataItem)成员 %>
<%#((Type)GetDataItem())成员 %>
这几种形式估计大家最不常用它们一般只使用与普通集合(例如数组ArrayListHashTable)和泛行集合(例如List<T>Dictionary<TkeyTvalue>)其实本质上就是实现了IListICollectionIEnumerableIDictionary等以及这些接口对应的泛行接口的集合IList接口和IDictionary接口的区别是一个只有值而另一个是键/值对对应泛行形式也是这样而Array就对用List<T>而HashTable就对应Dictionary<TkeyTvalue>