在這個系列的上篇中介紹了數據綁定語法的原理以及NET中如何實現單向綁定中篇我們簡單的介紹了ASPNET 中新增的Bind語法配合DataSourceControl來實現數據的自動雙向綁定這兩部分的內容相對動態抽象並且不常接觸沒有很好的源代碼支持很難解釋清楚要想真正弄清它們的內部原理還需要大家親自動手去反編譯分析動態編譯的程序集
在了解了數據綁定語法的原理後我還想來談談我中實踐過程中遇到的一些問題以及其它實用的綁定技巧首先我們就來說說特殊字段名的問題我們知道在數據庫當中如果表名或字段名中包含有一些特殊的不能是合法的字符時都會使用[]將它們引起來以便他們能夠正常使用但是在<%# Eval()%>的綁定語句當中同時可以使用[]但是對於字段名中包含 ()[]這個字符卻始終運行出錯假設像我下面這樣來綁定電壓(V)
<%# Eval(電壓(V))%>
那麼就會得到一個運行時錯誤
DataBinding:SystemDataDataRowView不包含名為電壓的屬性
表明括號是被認為是一個特殊字符那我們如果給字段名加上[]如下
<%# Eval([電壓(V)])%>
此時我們會得到另一個運行時錯誤
電壓(V 既不是表 DataTable 的 DataColumn 也不是 DataRelation
表明即使加上[]也無法解決這個特殊字段名的問題同時字段名中如果也存在中括號也是會出現這樣的問題的但是這樣的字段名卻在GridView的自動生成列中能被正常綁定呢?問題會出現在哪裡呢?分析和對比GridView的自動生成列與Eval這樣的綁定語法在最終執行綁定代碼上的不同我們可以發現GridView的自動生成列取值並不是使用DataBinderEval這個方法它內部有自己的取值方式但是在實現上卻是大同小異的那究竟是在哪裡出現了問題呢?我們找出DataBinder類的定義
: [AspNetHostingPermission(SecurityActionLinkDemand Level=)]
: public sealed class DataBinder
: {
: // Fields
: private static readonly char[] expressionPartSeparator = new char[] { };
: private static readonly char[] indexExprEndChars = new char[] { ] ) };
: private static readonly char[] indexExprStartChars = new char[] { [ ( };
:
: // Methods
: public static object Eval(object container string expression)
: {
: if (expression == null)
: {
: throw new ArgumentNullException(expression);
: }
: expression = expressionTrim();
: if (expressionLength == )
: {
: throw new ArgumentNullException(expression);
: }
: if (container == null)
: {
: return null;
: }
: string[] expressionParts = expressionSplit(expressionPartSeparator);
: return Eval(container expressionParts);
: }
:
: private static object Eval(object container string[] expressionParts)
: {
: object propertyValue = container;
: for (int i = ; (i < expressionPartsLength) && (propertyValue != null); i++)
: {
: string propName = expressionParts[i];
: if (propNameIndexOfAny(indexExprStartChars) < )
: {
: propertyValue = GetPropertyValue(propertyValue propName);
: }
: else
: {
: propertyValue = GetIndexedPropertyValue(propertyValue propName);
: }
: }
: return propertyValue;
: }
:
: public static string Eval(object container string expression string format)
: {
: object obj = Eval(container expression);
: if ((obj == null) || (obj == DBNullValue))
: {
: return stringEmpty;
: }
: if (stringIsNullOrEmpty(format))
: {
: return objToString();
: }
: return stringFormat(format obj);
: }
:
: public static object GetDataItem(object container)
: {
: bool flag;
: return GetDataItem(container out flag);
: }
:
: public static object GetDataItem(object container out bool foundDataItem)
: {
: if (container == null)
: {
: foundDataItem = false;
: return null;
: }
: IDataItemContainer container = container as IDataItemContainer;
: if (container != null)
: {
: foundDataItem = true;
: return containerDataItem;
: }
: string name = DataItem;
: PropertyInfo property = containerGetType()GetProperty(name BindingFlagsPublic | BindingFlagsInstance | BindingFlagsIgnoreCase);
: if (property == null)
: {
: foundDataItem = false;
: return null;
: }
: foundDataItem = true;
: return propertyGetValue(container null);
: }
:
: public static object GetIndexedPropertyValue(object container string expr)
: {
: if (container == null)
: {
: throw new ArgumentNullException(container);
: }
: if (stringIsNullOrEmpty(expr))
: {
: throw new ArgumentNullException(expr);
: }
: object obj = null;
: bool flag = false;
: int length = exprIndexOfAny(indexExprStartChars);
: int num = exprIndexOfAny(indexExprEndChars length + );
: if (((length < ) || (num < )) || (num == (length + )))
: {
: throw new ArgumentException(SRGetString(DataBinder_Invalid_Indexed_Expr new object[] { expr }));
: }
: string propName = null;
: object obj = null;
: string s = exprSubstring(length + (num length) )Trim();
: if (length != )
: {
: propName = exprSubstring( length);
: }
: if (sLength != )
: {
: if (((s[] == ) && (s[sLength ] == )) || ((s[] == \) && (s[sLength ] == \)))
: {
: obj = sSubstring( sLength );
: }
: else if (charIsDigit(s[]))
: {
: int num;
: flag = intTryParse(s NumberStylesInteger CultureInfoInvariantCulture out num);
: if (flag)
: {
: obj = num;
: }
: else
: {
: obj = s;
: }
: }
: else
: {
: obj = s;
: }
: }
: if (obj == null)
: {
: throw new ArgumentException(SRGetString(DataBinder_Invalid_Indexed_Expr new object[] { expr }));
: }
: object propertyValue = null;
: if ((propName != null) && (propNameLength != ))
: {
: propertyValue = GetPropertyValue(container propName);
: }
: else
: {
: propertyValue = container;
: }
: if (propertyValue == null)
: {
: return obj;
: }
: Array array = propertyValue as Array;
: if ((array != null) && flag)
: {
: return arrayGetValue((int) obj);
: }
: if ((propertyValue is IList) && flag)
: {
: return ((IList) propertyValue)[(int) obj];
: }
: PropertyInfo info = propertyValueGetType()GetProperty(Item BindingFlagsPublic | BindingFlagsInstance null null new Type[] { objGetType() } null);
: if (info == null)
: {
: throw new ArgumentException(SRGetString(DataBinder_No_Indexed_Accessor new object[] { propertyValueGetType()FullName }));
: }
: return infoGetValue(propertyValue new object[] { obj });
: }
:
: public static string GetIndexedPropertyValue(object container string propName string format)
: {
: object indexedPropertyValue = GetIndexedPropertyValue(container propName);
: if ((indexedPropertyValue == null) || (indexedPropertyValue == DBNullValue))
: {
: return stringEmpty;
: }
: if (stringIsNullOrEmpty(format))
: {
: return indexedPropertyValueToString();
: }
: return stringFormat(format indexedPropertyValue);
: }
:
: public static object GetPropertyValue(object container string propName)
: {
: if (container == null)
: {
: throw new ArgumentNullException(container);
: }
: if (stringIsNullOrEmpty(propName))
: {
: throw new ArgumentNullException(propName);
: }
: PropertyDescriptor descriptor = TypeDescriptorGetProperties(container)Find(propName true);
: if (descriptor == null)
: {
: throw new HttpException(SRGetString(DataBinder_Prop_Not_Found new object[] { containerGetType()FullName propName }));
: }
: return descriptorGetValue(container);
: }
:
: public static string GetPropertyValue(object container string propName string format)
: {
: object propertyValue = GetPropertyValue(container propName);
: if ((propertyValue == null) || (propertyValue == DBNullValue))
: {
: return stringEmpty;
: }
: if (stringIsNullOrEmpty(format))
: {
: return propertyValueToString();
: }
: return stringFormat(format propertyValue);
: }
:
: internal static bool IsNull(object value)
: {
: if ((value != null) && !ConvertIsDBNull(value))
: {
: return false;
: }
: return true;
: }
: }
其中我們可以發現有三個靜態只讀變量
private static readonly char[] expressionPartSeparator = new char[] { };
private static readonly char[] indexExprEndChars = new char[] { ] ) };
private static readonly char[] indexExprStartChars = new char[] { [ ( };
OK我們先不看代碼就應該知道問題就出在這個地方當我們分析哪裡用到indexExprEndChars時分找到這個方法
public static object GetIndexedPropertyValue(object container string expr)
我們不需要閱讀裡面的代碼通過下面的expr參數注釋我們就可以很快得到答案
expr 從 container 對象到要放置在綁定控件屬性中的公共屬性值的導航路徑此路徑必須是以點分隔的屬性或字段名稱字符串如 C# 中的 Tables[]DefaultView[]Price 或 Visual Basic 中的 Tables()DefaultView()Price
它告訴我們我們不僅可以使用字段名的方式同時還可以使用索引下標的方式來綁定字段值(C#和VB分別使用[]和()來取索引值)正因為如此我們才不可以在字段名中使用括號和中括號如上我們假設電壓(V)字段的索引下標是那麼我們可以像下面這樣綁定來解決特別字段名帶來的問題 <td><%# Eval([]))%></td>
上面的注釋同時還告訴我們是可以通過一個對象的導航路徑如 對象屬性子屬性 的方式來綁定一個數據項的間接屬性這個我們可以通過對expressionPartSeparator靜態字段的使用得以驗證
: public static object Eval(object container string expression)
: {
: if (expression == null)
: {
: throw new ArgumentNullException(expression);
: }
: expression = expressionTrim();
: if (expressionLength == )
: {
: throw new ArgumentNullException(expression);
: }
: if (container == null)
: {
: return null;
: }
: string[] expressionParts = expressionSplit(expressionPartSeparator);
: return Eval(container expressionParts);
: }
: private static object Eval(object container string[] expressionParts)
: {
: object propertyValue = container;
: for (int i = ; (i < expressionPartsLength) && (propertyValue != null); i++)
: {
: string propName = expressionParts[i];
: if (propNameIndexOfAny(indexExprStartChars) < )
: {
: propertyValue = GetPropertyValue(propertyValue propName);
: }
: else
: {
: propertyValue = GetIndexedPropertyValue(propertyValue propName);
: }
: }
: return propertyValue;
: }
前面的那個Eval重載把expression表達式用expressionPartSeparator字符分隔開然後調用內部的Eval(objectstring[])重載在這個重載中按順序去一級一級遞歸遍歷屬性值最終找到最後的那個綁定字段值所以我們是可以綁定跨級的間接屬性和關聯DataRowRelation行的值
還想在再來說說其它的綁定方式李濤在它的博客淺談NET中的數據綁定表達式(二)中提到了綁定數據的七種方式分別為
<%#ContainerDataItem%>
<%#GetDataItem()%>
<%#Eval(字段名)%>
<%#DataBinderEval(ContainerDataItem字段名)%>
<%#((DataRowView)ContainerDataItem)[字段名] %>
<%#((Type)ContainerDataItem)成員 %>
<%#((Type)GetDataItem())成員 %>
如果按要我來分的話我只會分成兩類強類型綁定和反射綁定不論是ContainerDataItem還是GetDataItem()都是得到當前的正在綁定的上下文數據對象然後轉換成他們的原始類型使用索引或強類型的方式來綁定字段值而Eval就是使用反射的方式來進行通用化的綁定這樣我們就完全沒有必要關心被綁定的數據源是什麼類型在很多場合下這是非常有益的
從性能方式來考慮強類型綁定肯定要比反射綁定性能來得好這其中的原因就不多作解釋了但是對於強類型來說是使用ContainerDataItem還是GetDataItem的方式來取得上下文數據對象性能應該差別不大的我們在前面已經提到到在Page的作用域內會把所有的被綁定(遍歷的數據項或整個集合)保存在一個堆棧方面我們來讀取我們只需要讀取堆棧的頂部元素就可以方便的得到當前正在被綁定數據行項而Container而更像是一個動態的關鍵字作用的變量因為你在綁定不同對象時Container的類型是不一樣的假設你當前正在綁定Repeater那麼它的類型是RepeaterItem只是為了方便我們強類型取得當前Repeater行對象而產生的動態屬性其實它並不是Page的一個公有或私有屬性所以我認為兩種取得DataItem的方式在性能上實際是沒有多大區別的
當然我們在選擇是使用強類型綁定還是反射綁定時主要還是取決你的需要我個人認為為了使用解決方案通用化而不必在關心綁定的數據類型是什麼類型應盡量使用Eval的方式來綁定字段在實踐當中綁定字段的消費上還不是非常多的為了靈活和通用這點性能損失我認為是值得的另外就是如上的特殊字段的情況我當然也可以使用強類型綁定的方式來解決
<%#((SystemDataDataRowView)ContainerDataItem)[電壓(a)]%>
特殊字段的解決之道有很多比如我們還可以重寫Page的Eval方法達到我們的目的選擇哪種方案就是取決於我們實際需要了
上面我們從特殊字段名出發分析了DataBinder在反射取得字段值時所做的一些特殊處理進而引出我們平常可能會被忽略的一些非常有用的綁定方式如索引下標綁定和間接字段綁定而這些對於我們解決一些疑難問題會有很大的幫助特別跨級的字段綁定如果我們沒有了解的話可能就需要在服務器代碼中做很多的類型轉換和處理最後我們還討論了其它的幾種綁定方式以及它們各種的性能和應用場合
三天用篇文章來分析了ASPNET在數據綁定的一個原理其中很多內容並不是我們平常數據綁定時所需要掌握的知識但是掌握了它們卻對我們在數據綁定時有更多的把握正因為內容的動態性和過於抽象而本人又無法找到一種最為合適的語言來組織和解釋這些知識代碼太多全部貼出來又感覺找不到重點貼重要的部分又感覺跨度太大所以三篇下來很多要領解釋的不是很清楚大家權當它是一個引子更多的原理還需要大家自己親自去分析和閱讀代碼
From:http://tw.wingwit.com/Article/program/net/201311/12894.html