数据集(DataSet)具有对并发性的内置支持,以及定义和处理表间复杂关系的能力。数据集也是可串行化的,并且如果相关数据库架构改变了,通常也不需要对DAL API进行更改。类似地,自定义对象的集合支持串行化,并且如果修改了数据库架构,也不需要任何方法签名的更改。集合也可以避免由数据集引起的性能损失。例如,当涉及到数千数据行时,数据集封送(marshaling)可能会出问题。数据集特别适合于移动数据集合;同样,对于主要需要实例数据和标量值的方法,以及基于自定义业务实体的方法也是最适合的。
DAL类检索的数据通常会被绑定到Windows和Web Forms控件,并且也通常用于Web服务请求。我发现,这里的大部分书籍和文章(包括我的)主要倾向于从数据集的角度介绍数据绑定,而下面我将考虑所有角度——Windows Forms、Web Forms和Web服务,逐一介绍数据绑定和自定义集合。但是在深入讲解之前,我们先快速复习一下.NET Framework 1.x中的集合,再粗略了解一下.NET Framework 2.0中的泛型。
集合基础
集合是通过容器类的接口来管理的一组同类对象。集合是一个类型化的实体;因此,所有包含的对象遵从相同的声明类型。.NET定义很多不同种类的集合;每一种映射到一种不同的数据结构——队列、堆栈、列表、散列表等等。下面我们来比较不同的集合类。
.NET Framework带有大量集合类,并且它们乍看起来都很像。所以,您如何才能确保为自己的任务找到了适合的集合类呢?集合越专门化,它的限制就越多,但是也更适合于特殊情形。表1列出了.NET Framework 1.x中使用的一些最常见的集合类。对于每一个类,可以看到它实现的主要接口和源命名(Source Namespace)空间。表2详细介绍了集合背后的接口的特征。
表1: .NET Framework 1.x中的集合类
类
接口
命名空间
说明
Queue
ICollection
System.Collections
一个先进先出 (FIFO) 的对象集合
Stack
ICollection
System.Collections
一个后进先出 (LIFO) 的对象集合
ArrayList
IList
System.Collections
一个弱类型化的对象列表
StringCollection
IList
System.Collections.Specialized
一个强类型化的字符串列表
Hashtable
IDictionary
System.Collections
一个基于键的哈希代码而组织的键/值对的集合
StringDictionary
IDictionary
System.Collections.Specialized
一个哈希表,其键被强类型化为字符串而不是对象
SortedList
IDictionary
System.Collections
一个已排序的键/值对的集合
ListDictionary
IDictionary
System.Collections.Specialized
单向链接的列表,专为一般只包含10项或更少项的集合而设计
NameValueCollection
IDictionary
System.Collections.Specialized
一个已排序的关联字符串键和字符串值的集合,其值可通过键或索引进行访问
表2:.NET Framework 1.x中的集合接口
接口
说明
IEnumerable
定义方法,以支持集合上的简单迭代
ICollection
向枚举集合添加计算大小、复制和同步等功能
IList
定义一个可按索引单独访问的对象集合
IDictionary
表示一个键/值对集合
集合是对象的枚举列表,这意味着它实现IEnumerable接口。IEnumerable接口本质上公开了一个enumerator对象,从而让for-each结构可以工作;更准确地说,以一种通用方式获得枚举的成员。除了IEnumerable之外,集合还实现诸如ICollection或IList之类的附加接口,以提供附加的功能。例如,ICollection为将值复制到外部数组而添加了Count属性和CopyTo方法。注意,尽管可以通过ICollection接口中定义的属性而随意进行同步,集合类一般不是线程安全的。
IList是对ICollection的扩展,增加了Add和Remove方法;IList还添加了Item属性,允许对条目进行索引后的数组式访问。IDictionary接口定义了一个表示键/值(key/value)对的集合的API。IDictionary公开方法类似于IList中的方法,但是具有不同的签名。所有词典都具有诸如Keys和Values这样的属性。
Array、ArrayList和StringCollection是.NET Framework中可用的集合类的实例。典型的词典类是ListDictionary、Hashtable和SortedList。表3为选择满足各人需求的.NET Framework 1.x 集合提供了一些提示。
表3: 选择集合类
类
说明
Queue和Stack
适合于即检索数据并需要以特定顺序一次一个元素地处理数据的场景。
Arrays
对于固定大小、带索引的集合是最有效的工具。注意,对于一维数组,编译器使用内置的微软中间语言(Microsoft intermediate language,MSIL)指令来更加快速有效地处理它们。
ArrayList
类似于数组,是固定大小数组的一个可增长的替代方案,可以存储任何种类的对象。注意,许多公共可用的集合类在内部使用一个ArrayList,并围绕它创建一个更友好的编程接口。
StringCollection
对于动态大小的字符串数组,这是一个比ArrayList更好的选择,因为它提供一个强类型化的接口。
Hashtable和ListDictionary
当需要基于键的访问时,它们是很好的选择。您可能想要选择ListDictionary来处理较少的项(20个或更少),而选择Hashtable来处理较多的项。
HybridDictionary
一个包装器类,开始是ListDictionary,当大小超过临界阈值时,就自动切换成Hashtable(由一些内部ASP.NET类所使用)。
StringDictionary
当集合只包含字符串时,这是一个比Hashtables更好的选择。
NameValueCollection
最慢的集合。只有在想要同时提供基于索引的和基于键的数据访问时,才应该使用此集合。
SortedList
Hashtable和数组的混合。在按键访问元素时类似于Hashtable,在使用索引时类似于数组。只有在需要维护已排序顺序时才使用这个类。
如何为数据绑定构建自己的集合类呢?自定义集合是强类型化的集合,公开了某个特定类型的对象。在ASP.NET服务器控件中,有很多自定义集合。例如,许多列表控件具有的Items属性的类型是ListItemCollection,它实现了ICollection。要创建自定义集合,需要编写一个实现ICollection的类,但是更好的选择是继承自CollectionBase,如代码段1中的示例OrderCollection类采用的就是这种方法。
代码段1:OrderCollection类
using System;
using System.Collections;
namespace Msdn.CuttingEdge.Samples
{
public class OrderCollection : CollectionBase
{
// Class constructor
public OrderCollection() {}
// Add method
public void Add(OrderInfo o)
{
InnerList.Add(o);
}
// Indexer property
public OrderInfo this[int index]
{
get { return (OrderInfo) InnerList[index]; }
set { InnerList[index] = value; }
}
}
}
OrderCollection类将OrderInfo类的实例组合在一起,后者又收集了很多描述订单的公共属性,就像SQL Server Northwind数据库中一样。代码段2展示了OrderInfo类的一个简单实现。它很简单,因为它只公开Orders表中的一些字段。在实际场景中,您可能想要将外键映射到实际的名称(雇员和客户),以及随意地使用更复杂的查询,以获得订单详细信息的子集合。
代码段2:OrderInfo类
using System;
using System.ComponentModel;
namespace Msdn.CuttingEdge.Samples
{
[Serializable]
public class OrderInfo
{
private int m_id;
private DateTime m_date;
private int m_employeeID;
private string m_customerID;
public OrderInfo() {}
public OrderInfo(int id)
{
m_id = id;
}
public OrderInfo(int id, DateTime date) : this(id)
{
m_date = date;
}
public int ID
{
get {return m_id;}
}
public DateTime Date
{
get {return m_date;}
set {m_date = value;}
}
public string CustomerID
{
get {return m_customerID;}
set {m_customerID = value;}
}
public int EmployeeID
{
get {return m_employeeID;}
set {m_employeeID = value;}
}
}
}
填充集合
在代码段2中,集合没有提供填充方法。您可以使用访问器类上的方法(可以是静态的)将数据物理地加载到集合的新实例中,或者扩展集合的构造函数以接受执行数据访问所需的任何参数,如下面的代码所示:
OrderCollection orders = new OrdersCollection(year);
通过将一些数据合并到集合中,数据容器与数据访问器之间的耦合更加紧密;但是从积极的一面来看,代码抽象程度可能也更高。换句话说,它可能会让DataSet类公开一个方法,以接受一个查询或存储过程名。您知道,.NET Framework中不是这样的,而是由适配器充当去耦数据访问器并提供填充功能。
在构建数据访问器的过程中,通常最好采用一组数据访问辅助组件。这些组件集中了常见的数据访问任务,比如管理数据连接、处理参数、执行SQL命令和存储过程。类似层的一个极好的例子是Microsoft Data Access Application Block。
泛型简介
.NET Framework 2.0中已不再有集合问题,因为该版本引入了泛型。泛型提供一些以强类型化方式实现ICollection和其他接口的类,以及一个由开发人员动态提供的类型。泛型类有一些特征与集合相同;例如,集合的行为是相同的,而不管包含的对象的类型如何。要在.NET Framework 2.0中构建集合,需要一行代码用于指出各种集合类型中的一种,包括枚举、集合、列表、词典或绑定列表:
public class OrderCollection : Collection<OrderInfo> {}
基于泛型的集合以典型的.NET Framework 1.x 集合所不具有的方式,提供可重用性、类型安全性和有效性的唯一组合。.NET Framework 2.0提供一个新的命名空间——System.Collections.Generic,它包含几个新的基于泛型的集合类(参见表4)。但是没有包括最有趣的类BindingList<T>,该类位于System.ComponentModel命名空间中。除了集合的基本功能之外,BindingList<T>还提供一个框架,用于编码典型的绑定功能,比如排序、搜索和CRUD(即create-read-update-delete)操作。因此,当不使用诸如DataSet和DataTable这样的ADO.NET容器时,BindingList<T>非常适合于在Windows和ASP.NET 2.0应用中绑定任务。
表4: .NET中具有泛型功能的集合类
泛型类
说明
对应的非泛型类
BindingList<T>
支持复杂数据绑定场景的列表
无
Collection<T>
为泛型集合提供基类
CollectionBase
Comparer<T>
为排序比较两个相同泛型类型的对象
Comparer
Dictionary<K, V>
一个键/值对的集合
Hashtable
IEnumerable<T>
一个可以使用for-each语句进行迭代的集合
IEnumerable
KeyedCollection<T, U>
一个带有键的集合
KeyedCollection
LinkedList<T>
一个双向链接的列表
无
List<T>
列表的基类
ArrayList
Queue<T>
对象的FIFO集合
Queue
ReadOnlyCollection<T>
泛型只读集合的基类
ReadOnlyCollectionBase
SortedDictionary<K, V>
一个已按键排序的键/值对的集合
SortedList
Stack<T>
一个简单的LIFO对象集合
Stack
绑定到ASP.NET控件
假设您有一组DAL组件,它们可以返回某一年发出的订单集合。通过以下代码可获得该信息,并将它绑定到数据绑定控件:
OrderCollection orders = Helpers.LoadOrders(1997);
DataGrid1.DataSource = orders;
DataGrid1.DataBind();
网格定义它的列如下:
<Columns>
<asp:BoundColumn DataField="ID" HeaderText="ID" />
<asp:BoundColumn DataField="Date" HeaderText="Date"
DataFormatString="{0:d}" />
</Columns>
如果您熟悉Northwind数据库的话,就知道Orders表不具有“ID”或“Date”列。网格列的DataField属性引用OrderInfo类上的公共属性而不是Orders表结果集上的列。绑定集合中数据成员的类型是OrderInfo。
对于绑定目的有一点很重要,即集合成员暴露其公共属性,而不暴露其公共字段。更确切地说,如果您重新定义在代码段2中所见的OrderInfo类,让其看起来像下面的代码一样,就会得到一个HTTP异常:
public class OrderInfo {
public int ID;
public DateTime Date;
public int EmployeeID;
public string CustomerID;
}
错误消息解释说,在所选择的数据源没有找到名为“ID”的字段或属性。这是因为.NET数据绑定中不支持字段——即使字段可能在类定义被标记为公共的,所以您必须求助于属性。字段与属性之间的差别是,字段为调用方提供对底层数据的直接读/写访问,而属性对底层数据的访问是通过get/set访问器筛选的。
一旦构建了自己的自定义业务实体来公开属性,ASP.NET数据绑定就是自动化的了,并且甚至页面调度也不会出现任何重大问题了。但是,对于排序,又将如何呢?
一般来讲,ASP.NET网格采用两种方式中的一种来排序数据:要么使用一个带有ORDER BY子句的新的数据库查询,要么使用一个DataView对象。前一种方法不需要Web服务器资源,并且对于排序可能是最快的一种技术。毕竟,SQL Server排序算法是经过良好优化的。但是,返回去向SQL Server请求已排序的数据不一定是明智之举,因为网络延迟、查询执行和数据封送时间都会影响总体性能。另一方面,如果使用ADO.NET DataView对象或者可排序的数组或集合在Web服务器上排序,就会大量使用Web服务器的内存,除非您缓存已排序的视图以备后用(参见 “在.NET Framework中排序”)
在.NET Framework中排序
排序不是快速的操作。大致上讲,O(N*LogN) 次比较操作通常是可从一般目的排序算法得到的最佳性能。我们下面深入研究一下ADO.NET DataView对象和数组中排序的内部实现。
DataView类工作在DataTable之上,维护着一个行视图索引,该索引是按一个表达式筛选的,并按列组合进行了排序。每当指定一个新的筛选器或排序表达式,该索引就被更新一次。对所选表行的引用被缓存在一个DataRowView对象数组中。数组使用QuickSort算法的实现进行了排序。
数组也使用QuickSort算法进行排序。QuickSort有两种稍微不同的实现。数组试图在所包含对象的类型上优化算法,并执行大量DataView结构和内部设计不再需要的异常处理和静态检查。
正如所提到的,QuickSort算法排序N项时平均进行O(N*LogN) 次比较。但是在最坏情况下,它进行O(N*N) 次比较。当所有项都以相反顺序排好序时,就会出现QuickSort的最坏情况。QuickSort实际上比其他O(N*LogN) 算法都要快得多,因为可以在各种体系结构上有效地实现它的内部循环。
这说明,不考虑所用算法的有效性,就不能轻易做出排序决策。排序方法应该考虑绑定和页面调度。概括地说,高速缓存通常是解决页面调度和排序问题的好办法。总之,对于高性能和高度可伸缩的应用程序,您可能需要考虑存储相同数据的预先排序的副本,要么是从SQL Server排序的,要么是通过DataView或数组排序方法排序的。当然,高速缓存也有缺点,比如过期数据以及采用不常用的数据填充缓存页。
要通过DataView排序,必须让数据存储在DataTable对象中。要排序将其数据存储在数组或ArrayList中的自定义集合,通常使用Array.Sort静态方法或ArrayList.Sort实例方法,其中用到了自定义比较器实现或者依赖于IComparable接口的包含对象的实现。代码段3展示了一个支持排序的OrderCollection类的增强版本。
代码段3:OrderCollection的支持排序的版本
public class OrderCollection : OrderCollectionBase
{
// Class constructor
public OrderCollection() {}
// Add sort capabilities
public void Sort(string sortExpression, OrderSortDirection direction)
{
SortInternal(sortExpression, direction);
}
protected virtual void SortInternal(
string sortExpression, OrderSortDirection direction)
{
OrderInfo[] rgOrders = new OrderInfo[this.Count];
this.InnerList.CopyTo(rgOrders);
Array.Sort(rgOrders,
new OrderComparer(sortExpression, direction));
Reload(rgOrders);
}
protected void Reload(OrderInfo[] rgOrders)
{
this.InnerList.Clear();
InnerList.AddRange(rgOrders);
}
}
该类的新版本的特征是具有一个新的Sort方法,该方法接收一个排序表达式(一般是属性名)和一个指明排序方向的值。这些参数用于初始化比较器类:
public class OrderComparer : IComparer
{
public OrderComparer(string sortExpr, OrderSortDirection direction)
{
...
}
...
}
IComparer接口的特征是具有单个Compare方法,该方法用于排序数组中的两个元素。在本例中,这两个元素是OrderInfo的两个实例。指定属性的值是由PropertyDescriptor类检索的,并进行比较以返回一个整数值。
因此,现在您可以处理DataGrid上的SortCommand事件并可以对绑定集合进行排序了。下面是一个代码片断:
void SortCommand(object source, DataGridSortCommandEventArgs e)
{
BindSortedData(e.SortExpression);
}
辅助方法(helper method)从ASP.NET缓存检索集合,并在指定的列上按指定的方向排序。接下来,它缓存已排序的版本并绑定到网格,如您在代码段4中的中见到的一样。
代码段4:排序与绑定
void BindSortedData(string sortExpression)
{
OrderCollection orders = (OrderCollection) Cache["MyData"];
if (orders == null) {
LoadData();
orders = (OrderCollection) Cache["MyData"];
}
orders.Sort(sortExpression, OrderSortDirection.Descending);
Cache["MyData"] = orders;
DataGrid1.DataSource = orders;
DataGrid1.DataBind();
}
为了完成对ASP.NET绑定的讨论,我们来回顾一下伴随update和delete语句发生的事情。与核心绑定类似,就地编辑实际上并不受您用作数据源的枚举对象的类型所影响。当涉及到表示时,让用于Edit和Delete操作的按钮列按照预期工作,并且ADO.NET对象不会出现其他问题。
更新后端数据源有两种选择:对Web服务器缓存执行更新或者直接对数据库执行更新。如果使用缓存数据的话,第一种方法比较适合;该方法要求提供一个Submit按钮(或者一种定时机制),以便让用户定期地持久存储更改。两个连续的提交之间出现的任何Web服务器失败都会导致数据丢失。当用户单击以编辑和更新网格行时,更改只被持久存储到ASP.NET缓存中(参见代码段5)。
代码段5:编辑并更新一个界限集合
void DataGrid1_UpdateCommand(object source, DataGridCommandEventArgs e)
{
// Get order being edited
OrderCollection orders = RetrieveData();
OrderInfo currentOrder = orders[e.Item.DataSetIndex];
// Get textboxes
TextBox txtOrderDate = e.Item.Cells[2].Controls[0] as TextBox;
TextBox txtEmployeeID = e.Item.Cells[3].Controls[0] as TextBox;
if (txtOrderDate != null)
currentOrder.Date = DateTime.Parse(txtOrderDate.Text);
if (txtEmployeeID != null)
currentOrder.EmployeeID = Int32.Parse(txtEmployeeID.Text);
DataGrid1.EditItemIndex = -1;
BindData();
}
如果要是现每当用户退出编辑模式时发起对数据库的直接更新,需要在DAL上调用一个方法,该方法以一个用新值填充的OrderInfo作为参数,并用它的内容来准备和执行存储过程或SQL命令。
最后,如果您使用自定义集合来代替经典的ADO.NET对象,那么ASP.NET数据绑定不会改变太多。即使从模板角度来看,绑定也几乎是相同的。数据绑定表达式看起来是相同的,只是数据源的类型不同。DataBinder.Eval的字段名参数可以是列的名称或者是公共属性。排序要求稍微多一点的精力,因为需要您实现可排序的集合。从性能方面来讲,DataView和数组基本上差不多,因为两者都使用QuickSort算法。此外,基于数组的代码稍微要快一些,因为ADO.NET DataViews使用了较多的代码和辅助对象。
绑定到Windows Forms
在设计DAL时,通常不会假设调用应用程序是ASP.NET页面、基于Windows的应用程序或者Web服务。另一方面,DAL只是一些具有给定编程接口的.NET类。DAL也可以驻留在远程机器上,如果这样的话,为了使数据传输成功完成,您需要确保集合条目(即OrderInfo)是可串行化的。同样,在Windows Forms应用程序中,集合不存在绑定和页面调度操作方面的重大问题。那么,对于排序又将如何呢?有趣的是,如果使用下面的代码片段来填充Windows DataGrid,那么除了不支持排序以外一切正常:
OrderCollection orders = Helpers.LoadOrders(1997);
dataGrid1.DataSource = orders;
除了AllowSorting属性被设置为True以外,尝试单击任何列标题并验证没有任何事情发生。现在尝试将同一表单中的另一网格绑定到任何ADO.NET对象。奇怪的是,网格现在能够排序了。那么,到底发生什么啦?
从设计上来讲,Windows Forms DataGrid不像Web DataGrid和ASP.NET 2.0中的DataGridView控件一样,公开一个公共排序API。要自定义排序,您可以围绕网格编写一些代码,捕捉用户对标题栏的单击,计算出底层列,并手动进行排序。这并不是不可能的,但也不是一个很容易就能完成的工作。关键问题是:为什么自定义集合上不支持DataGrid排序?
从设计上来说,Windows DataGrid对排序依赖于IBindingList接口上的方法。如果绑定集合没有实现该接口,那么网格就不会排序数据。在.NET Framework 1.x中,只有两个类实现了IBindingList:DataView和DataViewManager。当您绑定一个DataTable或DataSet时,控件为绑定自动地抽取默认视图和使用DataView对象。
通常,Windows Forms应用程序使用批更新(Batch Update)将内存中的更改持久存储到远程数据库。与数据适配器一起,数据集提供用于处理批更新和实现并发性开放式窗体的功能。相当复杂的类似机制对自定义集合不可用,需要时应该从头开始构建。但是有了BindingList<T>,它就相当直观了。
绑定到Web服务
数据集(DataSet)通常用于从Web服务方法返回数据——不推荐这样做,因为它会导致紧耦合的设计,并且几乎不提供互操作功能。
数据集是XML可串行化的,这意味着它可以作为Web方法的输入参数或返回值。.NET为数据串行化提供两种主要机制——通过格式化程序(Formatter)实现和通过XML串行化程序(XML Serializer)实现——每一种都用于不同的应用程序场景。格式化程序可以串行化任何标记有[Serializable]属性的.NET对象;XML串行化程序是一个专门的组件,负责对Web服务方法封送参数和返回值。
格式化程序被设计成用于类型保真(Type Fidelity)和保存对象的完整状态。XML串行化程序使您限定于持久存储被串行化对象的公共接口,以及创建一个只有公共属性和字段不支持循环引用的XML实现。XML串行化程序也适合于实现IXmlSerializable接口的类型,包括众所周知的DataSet类型。
注意,.NET Framework添加了两种方式来增强数据集串行化。首先,数据集的二进制串行化提供更加紧凑的表示,但是使您只能进行.NET到.NET的通信。此外,新的SerializationMode允许开发人员跳过类型化数据集上的架构串行化。这对于发送有效负载的开发人员的影响非常巨大。对于有效负载数据集,90%的数据将会是架构,而这对于两边都是已知的。
数据集表示数据表的一个集合,并提供将多个结果集组合到单个实例中的机会。在Web服务上下文中,当您需要获得一些数据将之呈现给用户,并返回以应用更改时,DataSet类型是有用的。注意,在.NET Framework 1.x中,DataSet是唯一可与Web服务方法一起使用的ADO.NET对象,因为只有它实现了IXmlSerializable。在ADO.NET 2.0中,DataTable已经扩展成实现了相同的接口。
DataSet是一个多态类型,其布局只是在运行当中当对象填充了数据时才确定的。可以嵌入架构信息,但是这一般无助于Web服务工具包将它映射到特定于平台的类。所以只要从.NET客户端使用eb服务,数据集就是很适合的。在非.NET环境中,在处理数据集返回值时很容易出问题,并且需要平台的低级XML API。要解决这些问题,您通常为了与其他平台有更好的兼容性而编写WebMethod方法,使它们接收和返回数组或集合。自定义类的数组和集合也大大减少了网络的工作负载,因为它们需要很少的空间来串行化到XML。下面是一些Web方法的例子,它们在不同的签名中包含了相同的核心功能。
[WebMethod]
public DataSet GetOrdersAsDataSet(...)
[WebMethod]
public OrderInfo[] GetOrdersAsArray(...)
[WebMethod]
public OrderCollection GetOrdersAsCollection(...)
[WebMethod]
public string GetOrdersAsXml(...)
在Windows和Web Forms应用程序中,您可以添加对Web服务的引用以便生成客户端代理,在该代理上调用方法,以及绑定生成的值。当返回值是数据集时,绑定没有任何问题。如果Web方法返回自定义结构的数组或集合的话,那会怎么样呢?
当您向自己的项目导入一个Web服务引用时,Visual Studio® .NET会创建一个代理类来管理出站调用(Outbound Call)。该类也导入所有的类声明,以用于完成调用。
不管像OrderInfo这样的自定义类是如何由Web服务源代码定义的,它都可以由.NET Framework命令行工具wsdl.exe,使用字段而不是属性来重新构建(但是在.NET Framework 2.0中,属性是默认生成的)。正如前面所提到过的,缺乏属性的类的数组或集合不能绑定到Windows和Web控件。
要解决这个问题,您要确保Windows和Web客户端为Web服务所使用的自定义业务实体包含一个正确的定义。正确的定义是一种使用公共属性代替公共字段的定义。有两种方式可以做到这一点:可以编辑字段生成的代理文件以便用属性代替字段;也可以从引用文件删除所有的类定义,并通过一个附加的类文件将它添加到项目。注意,代理实现文件在Visual Studio .NET中一般是隐藏的,并且会对您显示因编辑它而带来的潜在风险的警告。但是,如果您知道怎么做的话,就请放心地去编辑它。对Web方法签名或类结构的任何更改,都可能需要编辑代理实现文件或者任何所使用的其他辅助类。
结束语
在大多数数据绑定场景中,自定义集合是对ADO.NET容器对象的一个有效替代。使用自定义业务实体代替数据集既有优点也有缺点,但是这肯定是一个值得探索的选择。绑定集合到Windows和Web Forms除了与排序有关的问题之外,没有什么重大的问题。对于Web服务,集合需要一些编码,这是由于使用公共字段代替公共属性的Visual Studio .NET导入程序工具的局限性所致。一般来说,数据集易于使用,但是不一定是最佳解决方案。集合是轻量级对象,从互操作观点来看也是可取的。