在ASP.NET框架中,有多种丰富的数据控件,例如GridView,它们在使用DataSourceID参数绑定时表现最佳。如果直接连接到SQL数据库,使用SqlDataSource对象,这些控件能够很好地工作。对于内存中的数据绑定,比如业务对象,应该提供绑定工具ObjectDataSource。作者尝试使用ObjectDataSource工具进行数据绑定,该工具的主要参数只定义了绑定对象的类型——它的临时实例是在绑定操作期间创建的,或者操作由类型的静态函数提供,而不需要创建对象。作者在实现这些临时对象和/或静态函数与Web页面上的其他对象之间的交互时遇到了重大问题,尤其是当最终数据源是一些通用集合,如泛型List<...>时;而绑定传统的DataTable则能提供更好的结果。尽管如此,DataTable的丰富功能为旧数据和修改后的数据行提供了变化。
背景:与其创建特定的数据结构并将一些业务对象集合转换为通用集合,不如将其转换为传统的.NET数据结构DataTable。当绑定到丰富的控件时,DataTable确保了它们的最大功能,而不需要编写复杂的用户代码。这种反射的结果是一个简单数据绑定元素的想法,它位于丰富的控件和表格之间;用户通过控件更新数据,程序最终将DataTable中存储的更改转换为实际的数据对象。自然地,如果业务数据直接存储在DataTable或其集合-DataSet中,或者在数据转换简单的结构中,那将是最优的。
作者对所描述控件的使用概念是填充数据表,用户更新它,最后通过相应的按钮(例如COMMIT)解决数据更改(即保存业务对象中的更改并调用DataTable.AcceptChanges()),或者忽略它们(调用DataTable.RejectChanges())。或者更一般地说:自定义数据源控件或简单地*.aspx页面可以最初从业务对象集合传输数据到表格,然后使用丰富的数据控件更新它,最后要么将数据传回业务对象集合(例如选择COMMIT或APPLY按钮),要么回滚所有更改(例如选择IGNORE按钮)。
整个项目是基于MSDN库中的一个示例——类CsvDataSource和Web演示(以及用户评论)的Nikhil Kothari。自然地,实现这些想法通常会导致比最初看到的更复杂的软件。所呈现的项目展示了两种方法:在单个GridView控件中直接编辑数据,使用TableDataSource绑定其列(TestTableDataSource.aspx)。在GridView中选择数据行,并在DetailsView控件中编辑它们,使用两个TableDataSource将单个表绑定到两个控件(TestTableMasterDetail.aspx)。所呈现的示例基于特定条件,每个表都有一个整数身份列作为键;它可以简单地适应更一般的情况。它基于简单的逻辑,可以很好地理解;作者不是该领域的专家。欢迎合理的评论和改进。
示例项目是从解决方案WEB.sln开始的。它由两个项目组成:创建库TableDataSource.dll的控件库项目和位于WEB文件夹中的自己的网站(两个页面)。
基本组件:所呈现的示例由这些基本组件组成:
类TableDataSource基于系统类System.Web.UI.DataSourceControl,实现了基本功能。补充类TableDataSourceView基于补充基类System.Web.UI.DataSourceView,实现了从TableDataSource到绑定控件的自己的数据绑定。两个Web页面TestTableDataSource.aspx和TestTableMasterDetail.aspx实现了在绑定表中直接在GridView和相关GridView和DetailsView的主-详细模式中更新数据。两个DataGrid自然地启用了分页和列排序,这几乎不需要额外的代码。
辅助枚举类型Gender说明了在丰富的控件中使用用户定义的类和模板列。辅助泛型静态函数BindEnum
最后两个组件作为源文件TableDataSource.cs的一部分呈现,其中也包含了用户组件TableDataSource的两个基本类的源代码。
类TableDataSource声明了这些基本元素:
默认构造函数。属性Table的类型是DataTable,通常由外部程序设置,并存储对DataTable对象的引用。当设置此属性时,对象绑定到具有相应更改的Table对象的事件。属性RowFilter的类型是string。这个字符串表示行选择过滤器的常量部分,而变量部分由下一个参数确定。当设置属性时,只分配其常量部分,而在get时,它返回完整的WHILE查询条件。参数集合
方法DataSourceView GetView(string viewName)实现了自己的数据绑定,将类DataSourceView的实例返回到绑定的丰富数据控件。内部int属性InsertedKey是帮助设置插入列的临时唯一负键;最终值在最终插入期间设置。
私有事件处理程序tableDataChanged(object source, EventArgs e)通过调用其函数RaiseDataSourceChangedEvent(e)将表更改事件传递到基类。
类TableDataSourceView这个类由基类内部使用,并声明了这些基本元素:构造函数public TableDataSourceView(IDataSource owner, string name)类似于基类。所有者是相应的TableDataSource对象,名称来自其ID,主要用于调试。绑定需要属性public static string DefaultViewName = "TableSourceView"。
重写的虚拟函数System.Collections.IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)实现了自己的数据传输到丰富的控件。返回从绑定表派生的DataView,带有来自参数的排序表达式和来自视图所有者属性的RowFilter。
重写的虚拟函数ExecuteUpdate(System.Collections.IDictionary keys, System.Collections.IDictionary values, System.Collections.IDictionary oldValues)实现了单行数据的更新。重写的虚拟函数ExecuteDelete(System.Collections.IDictionary keys, System.Collections.IDictionary oldValues)实现了单行数据的删除。重写的虚拟函数ExecuteInsert(System.Collections.IDictionary values)实现了单行数据的插入。
结论:所呈现的数据控件只是概念的原始实现,在标准的.NET数据源控件中可以更好地实现。它展示了基本原则,而细节可以根据最初的经验进行补充。作者感谢任何读者的评论和建议,以改进所呈现的材料。
更正:完成后,发现了一些改进,使得在RowFilter更改后可以自动重新绑定——它在OnPreRender中进行了测试,然后控件通知绑定的元素。感谢Ebrahimhassan的宝贵讨论。发现了一些由不正确管理null和DBNull值引起的错误——相信现在它工作得更好。所呈现示例的功能限于环境中的应用条件和规则:所有表都有唯一的主键,具有单个数值——更一般的情况留给读者。例子的主要原因是展示原则,而不是最终解决方案。
新版本允许在主-详细模型中插入的行打开以进行编辑——它说明了绑定网格中新一代的PageIndex和SelectedIndex。开源控件有一个显著的优势——它的用户可以在其关键点(例如在所呈现的示例中的ExecuteSelect,ExecuteUpdate...)设置断点,并研究开发代码的行为。它可以用作有价值的调试工具。