Dataview and Filter 视图和筛选器(深入研究数据访问)

来源:互联网 发布:电脑直播变声软件 编辑:程序博客网 时间:2024/04/30 03:18
*

ADO.NET 中有一层对象,它们专用于为您正在使用的任意数据源创建抽象模型。这样的集合包括如 “数据集”“数据表”DataRow“数据视图”DataRelation 等的对象。

所有这些对象都是在 System.Data 命名空间中定义的。无论您的目标是什么编程环境 - Windows 窗体、Web 窗体还是 Web 服务,它们形成的这个抽象模型都可以使您使用相同的编程接口。

在实际的应用程序中,这些对象大多最终会操作和呈现从 SQL Server 之类的关系型 DBMS 中提取的数据。不过,可以向它们填充任何数据,而不必考虑物理存储媒体。

您可以使用 “数据集” 对象来打包和关联数据表。只要您需要处理数据表格,就可能通过全套编程技巧来充分利用 DataTable 类。DataRow 对象可用于通过编程方式呈现表中任何一行的数据。

这三个对象都会对数据进行打包,但具有不同的逻辑聚合级别。“数据集”“数据表” 和其他元素构成的集合。“数据表”DataRow 和其他元素构成的集合。DataRow 是字段和其他元素构成的集合。不过,开始对包含的数据进行筛选和排序时,这些对象都没有相应的内置功能。

ADO.NET 提供了一些类来帮助完成数据驱动的应用程序这一极其重要方面的操作。截止到 .NET Beta 2,这些对象中最重要的两个是 “数据视图”DataViewManager

注意,DataViewManager 是 Beta 2 特有的类。在 Beta 1 中,相应的功能由 DataSetView 完成。

自定义数据视图

DataView 类用于表示 “数据表” 的自定义视图。“数据表”“数据视图” 之间的关系受众所周知的设计模式 - 文档/视图模型规则所控制。其中,“数据表” 充当文档,而 “数据视图” 则作为视图。

在任何时候,对于相同的基础数据您都可以有多个不同的视图。更重要的是,对于每个试图您都可以将其作为具有自己的一套属性、方法和事件的独立对象进行管理。这也代表了相对于 ADO 的一个巨大飞跃。

ADO 记录集是您能够定义筛选字符串。一旦您设置了这种字符串,则只有匹配所指定条件的记录才能用于进行读写。Filter 属性的工作原理与动态的 WHERE 子句很相似。它只是从同一记录集对象上的视图隐藏了某些记录。

在 ADO 中,您从来没有独立的视图对象。一个经过筛选的记录集总是同一个对象,只不过显示的记录比它的实际数量少一些而已。

如果您不需要同时操作几个不同的视图,上述问题就不是什么大问题。编程接口使得记录集既可以是表,也可以是一个具体的视图。不过,在创建时,两者不可能同时发生。在任何时刻,记录集要么是没有设置筛选字符串时的表,要么是激活了筛选字符串的视图。

记录集克隆提供了一个处理这种结构限制的很好方法。正如克隆和 多利 表案例,第一部分中解释的那样,克隆记录集是一个相对轻量型的操作,因为它不复制数据,只是复制记录集的基础结构。要管理相同数据的两个或多个视图,您可以利用两个或多个克隆,其中每个克隆都设置相应的筛选字符串。


图 1. 在 ADO 中管理同一记录集的不同视图

在 ADO.NET 中,您可以依赖于为即席 “数据视图” 对象提供的新设计的对象模型。ADO.NET 的 “数据视图” 对象表示某个给定数据表的自定义视图,但您可以将其作为单独的对象进行处理。“数据视图” 保留了对表的引用,并允许更新操作。


图 2. 在 ADO.NET 中管理同一数据表的不同视图

从功能上来说,使用 ADO 记录集克隆或特殊的视图对象完成的是同样的功能,因为两者都使您能够应用筛选,对所选的行进行操作,以及管理多个视图。

而在设计有效性、灵活性甚至是简洁性方面,毫无疑问,ADO.NET 方法更加卓越。

深入研究 DataView 对象

“数据视图” 对象是一个从 MarshalByValueComponent 继承的类,它会实现一些帮助器接口,从而使得该对象本身可用于数据绑定控件。

Public Class DataView
Inherits MarshalByValueComponent
Implements IBindingList, IList, ICollection, IEnumerable, _
ITypedList, ISupportInitialize

从 MarshalByValueComponent 派生的类是 .NET 远程组件,可以通过值进行封送处理 - 即将对象序列化到目标应用程序域。(有关 .NET 组件类的更多细节,请参阅后面的“对话栏”。)

“数据视图” 的内容可以通过各种编程接口进行滚动,其中包括集合,列表和枚举器。IBindingList 接口确保该类会提供用于同时支持到数据源的复杂绑定和简单绑定的所有必要特性。

总之,可以利用 “数据视图” 对象达到两个基本的目的。第一,视图对于将 “数据表” 对象关联到数据绑定控件的 “数据源” 字段是很重要的。第二,它表示链接的 “数据表” 上面的一层屏蔽,您可以通过它来对其中的内容进行筛选、排序、编辑和导航。

“数据视图” 并不是唯一可以通过值进行远程操作的数据驱动类。例如,“数据集”“数据表” 也具有同样的能力,可使它们成为非常有趣的对象,特别是在互操作的情况下。

创建 DataView

“数据视图” 有两种构造函数。在 C# 中,这些构造函数如下所示:

public DataView();
public DataView(DataTable);

“数据视图” 只有在链接到一个现有的、可能是非空的 “数据表” 对象后才会变得可用。通常,这种情况会直接在构造函数级别发生。

DataView dv;
dv = new DataView(theDataSet.Tables["Employees"]);

不过,您也可以先创建一个新的视图,然后再使用 “表” 属性将其与表相关联。

DataView dv = new DataView();
dv.Table = theDataSet.Tables["Employees"];

利用 “数据视图” 构造函数,您可以从 “数据表” 获得 “数据视图” 对象。如果需要,这个过程也可以反过来。实际上,DataTable 对象的 “默认视图” 属性会返回一个初始化的 “数据视图” 对象,以便在该表上进行操作。“默认视图” 对象在内部进行缓存的,每次都是作为新对象创建的,因此,输入的任何更改都在以后进行检索。

DataView dv = dt.DefaultView;

一旦您有了 “数据视图” 对象,您可以利用它的属性来建立希望用户看到的行集了。基本上,您一般会使用下面两个工具:

RowFilter

利用前者,您可以指定视图中可见行必须匹配的条件。而后者则采用一个表达式,然后按表达式对行进行排序。当然,您也可以使用这两者的任意组合。

设置筛选器

RowFilter 是一个读/写属性,它会获取和设置用于筛选表的表达式。

public virtual string RowFilter {get; set;}

您可以为它分配由列名、逻辑运算符、数字运算符和字面值的任意有效组合组成的表达式。下面是几个示例:

dv.RowFilter = "Country = 'USA'";
dv.RowFilter = " EmployeeID > 5 AND Birthdate < #1/31/82# "
dv.RowFilter = "Description LIKE '*product*'"

我们来看一下筛选器的基本规则和运算符。

筛选字符串是表达式的逻辑串联。可以使用 AND、OR、NOT 将一些较短的表达式连在一起。也可以使用圆括号来组合子句,并强制某些运算优先进行。

通常,任何子句都包含一个与字面值、数字、日期或另一个列名进行比较的列名。为此,可以使用关系运算符和算术运算符,如 (取模)等等。

如果要选择的行所遵循的模式很难通过算术运算符或逻辑运算符来呈现,则可以使用 IN 运算符。下面的代码显示了如何选择一个随机的行集:

dv.RowFilter = "employeeID IN (2,4,5)"

还支持通配符 * 和 %,这些通配符经证明非常有用,尤其是与 LIKE 运算符一起使用时更是这样。这两个通配符都表示任意数量的字符,可以相互替换使用。

请 注意,如果某个 LIKE 子句中的字符串已经包含了 * 或 % 字符,则必须用方括号 ([]) 将其括起来,以进行运算符转义。如果碰巧该子句中也包含了方括号,则还必须对用于运算符的这些方括号进行转义。因此,将产生一个不是很容易读懂的匹配字符 串,如下所示:

dv.RowFilter = "Description LIKE '[[]*[]]product[[]*[]]"

通配符只允许在筛选字符串的开头和/或结尾处出现。不能在字符串中间使用通配符。例如,下面的代码行会产生运行时错误:

dv.RowFilter = "Description LIKE 'prod*ct"

字符串必须用单引号括起来,而日期必须以 # 符号括起。如果需要,数值可以使用小数点和科学计数法。

RowFilter 还支持聚合函数,如 SUM、COUNT、MIN、MAX 和 AVG。如果表中没有行,这些聚合函数则返回一个空引用。

最后,我们来看三个非常方便的函数:LenIIFSubstring,完成对于 RowFilter 表达式的讨论。

正如其函数名所表示的那样,Len() 将返回指定的表达式的长度。该表达式可以是一个列名,也可以是任何其他有效的表达式。

Substring() 将返回一个指定长度的子字符串,该字符串自给定表达式指定位置处开始。

我最喜欢用的是 IIF(),它会根据给定逻辑表达式的结果,采用两个值中的一个值。IIF 是 IF-THEN-ELSE 语句的缩写形式。语法如下:

IF(expression, if_true, if_false)

使用该函数可以构建非常复杂的筛选字符串。例如,假定您从 SQL Server Northwind 数据库提取 Employees 表,下面的表达式则使您能够只选择出那些 ID 小于 6 且姓为偶数个字符以及 ID 大于 6 且姓为奇数个字符的员工。

   IF(employeeID<6, Len(lastname) %2 =0, Len(lastname) %2 >0)  

下图显示了结果。(稍后将讨论这个示例应用程序。)


图 3. 对 Northwind 表应用筛选器

这个示例应用程序是一个 Windows® 窗体应用程序,它使用了两个 datagrid 控件来实现一个简单的主/详细架构。第一个表格在加载时进行填充,即在 SQL Server 数据适配器完成数据提取任务之后立即进行。注意,数据适配器是在 Beta 2 中引入的概念。它相当于 Beta 1 中的 SQLDataSetCommand 类。

预排视图

在上面的示例中,datagrid 负责预排视图中的行,以便刷新用户界面。这个自动机制是 .NET 数据绑定的产物。Datagrid 是一个通过 DataSource 属性进行填充的数据绑定控件。“数据视图” 是一个可绑定数据的类,它构成了 DataSource 属性的有效内容。

如果您想使用其他控件而不使用 Datagrid,应该怎么办呢?如果您不想利用自动数据绑定呢?应该怎样预排视图中所选的行呢?

DataView 的 “表” 属性会引用基础表,但 “数据表” 并不保存关于筛选的信息。所以,预排表中的行注定是会导致失败的。虽然 “数据表”“数据视图” 是紧密相联的,但它们仍然是各自独立的对象,具有不同的功能。

以下 Visual Basic .NET 代码断显示了如何遍历视图中的所有行,以便向某个列表框添加字符串。

Dim dv As New DataView()
dv = ds.Tables("Employees").DefaultView
dv.RowFilter = "employeeid >5"

ListBox1.Items.Clear()
Dim buf As String
Dim dr As DataRowView
For Each dr In dv
buf = ""
buf &= dr("lastname").ToString() & ", " & dr("firstName").ToString()
ListBox1.Items.Add(buf)
Next

正如前面所提到的,“数据视图” 已然是一个可枚举类了,因此您可以安全地将它传递给 For..Each 语句。如果您希望实现 For..Next 循环,Count 属性则存储视图中的行数。

要访问视图中的某一行,可以使用 DataRowView 类而不是 DataRowDataRowView 表示 DataRow 的视图,其表示方式在很大程度上与 “数据视图” 表示 “数据表” 的自定义视图的方式相同。

一般说来,DataRow 最多有四种不同的状态:default、original、current 和 proposed。这些状态可由 DataRowVersion 枚举类型进行收集以及由 DataRow 的 RowVersion 属性进行表达。

DataRow 的视图只能显示其中一种状态。

只有在构造时某些列设置了默认值时,行的默认版本才会呈现。原始版本是指在最后一次调用表的 AcceptChanges 时,从数据源中得到的行或是它的快照。当前版本是指当前的行,包括当时发生的所有更改。Proposed 状态只存在于调用 BeginEdit 和 EndEdit 的编辑过程中。

您可以使用相同的 DataRow 语法访问 DataRowView 的内容。这个关键属性是名为 Item 的索引器属性。

排序和其他方便的特性

“数据视图” 具有一个 “排序” 属性,您可以用它对视图的内容排序。“排序” 采用用逗号分隔的列表达式列表,并且按该列表对视图进行排序。通过在任何列表达式后添加一个 ASC 或者 DESC 限定符,可以对字段按照指定的升序或降序排列。如果不指定方向限定符,排序算法则默认为 ASC。

“数据视图” 是内存中的对象,所以排序在本地进行,无需调用数据库服务器。

DataView 类另一个有趣的属性是 RowStateFilter。有了这个属性,您可以使用任何预定义的标准来筛选 “数据表” 的内容。下表包括了 DataViewRowState 枚举类型的所有值:

   

CurrentRows

包括未更改的行、新行和修改的行的当前行。

Deleted

自上次调用 AcceptChanges 后所有删除的行

ModifiedCurrent

自上次调用 AcceptChanges 后所有修改过的行。

ModifiedOriginal

自上次调用 AcceptChanges 后已修改的原始版本的行。

New

自上次调用 AcceptChanges 后所有新添加的行。

OriginalRows

返回原来的行集。其中包括未更改的行和删除的行。

Unchanged

所有未受更改影响的行。

如果要使用断开连接的数据,所有更改只会在对 “数据表” 调用 AcceptChanges 后生效。对单个行的更改可以通过调用 DataRow 的 AcceptChanges 使其生效。同样,这些更改可以通过对 DataTable 类(对于所有行)或单个 DataRow 对象调用 RejectChanges 来取消。

“数据视图” 对象还有一些属性,如 AllowEditAllowDeleteAllowNew,这些属性用于设置或获取指示是否允许更改的值。它们的默认值设置为 True,即允许任何种类的更新。如果您试图在相应的标志设为 False 时完成更新操作,则会发生运行时错误。

DataView 管理器

“数据表” 对象有一个 “默认视图” 属性,它会返回一个 “数据视图” 对象作为表内容的默认视图。Dataview 在默认视图中是进行排序的 - 按照提取期间设置的记录的自然顺序 - 在不应用筛选器的情况下列出表中所有行。也就是说,DataView 包含对 DataTable 行的引用,并且按照最初提取的顺序存储行。

theMasterGrid.DataSource = m_ds.Tables("Employees").DefaultView

如果需要某个特殊的数据视图,您可以对视图排序和/或直接对 “默认视图” 对象应用筛选。

m_ds.Tables("Employees").DefaultView.Sort = "lastname"
theMasterGrid.DataSource = m_ds.Tables("Employees").DefaultView

DataViewManager 类用于存储 “数据集” 中每个表的视图设置。

可以通过将一个有效的非空 “数据集” 传递给类构造函数来创建 DataViewManager

Dim dvm As DataViewManager
dvm = New DataViewManager(m_ds)

也可以使用 DefaultViewManager 属性直接从 “数据集” 对象获取:

Dim dvm As DataViewManager = m_ds.DefaultViewManager

重要的是,DataViewManager 类与 “数据集” 相关联。下面是另一种可行的方法:

Dim dvm As New DataViewManager()
dvm.DataSet = m_ds

DataViewManager 的关键属性是 DataViewSettingsDataViewSetting 对象的集合。

Dim dvs As DataViewSetting
dvs = dvm.DataViewSettings("Employees")
dvs.Sort = "lastname"

DataViewSetting 对象包含有关表视图参数的信息。将数据绑定到具有数据识别能力的控件时,使用 DataViewManager 而不是 DataSet 或 DataTable 可以保留您的视图设置(筛选和排序字段)。

theMasterGrid.DataSource = dvm
theMasterGrid.DataMember = "Employees"

在这种情况下,视图会按照 DataViewSetting 中为 Employees 表指定的设置自动进行排序和筛选。也就是说,DataViewSetting 类表示用于特定于表的视图设置的某种缓存。

下一步

上面讨论的应用程序示例使用筛选器实现了主/详细架构。只要使用 .NET 特有的数据绑定控件(例如 datagrid),就能够通过更好的方式获得同样的结果。在以后的专栏中,我将讨论内存中的数据关系,以及它们是如何影响主/详细架构的设计的。

 

对话栏:您需要控件还是组件?

在 .NET 中,有许多术语经常可以相互替换使用(虽然处于较早阶段,但是比较有价值)。这里特别指出的是组件对象控件。并非过分注重细节,但我希望有一个对每个术语都给出适当含义的表。因为我们根本不能确定它们只是同义词。

需要牢记在心的要点是,整个 .NET 框架是由类组成的。因此您从中获得的任何内容,首先是。也就是说,在 .NET 环境中,控件和组件不是同一种类。至于对象,可以认为它们是正在运行 .NET 类的实例。 组件是一个实现 IComponent 接口的特殊类,或者是一个派生的类,即派生于实现 IComponent 的类。 控件是一个提供用户界面功能的组件。在 .NET 框架中,可以找到两个基本类别控件:客户端的 Windows 窗体控件和 ASP.NET 服务器控件。 IComponent 接口包含 IDisposable 接口,并提供一种清除资源的决定性方法。

Public Interface IComponent
Inherits IDisposable

这种释放资源的方法是标准的 .NET 垃圾收集器的替代方案。通过实现 IDisposable,您定义了一个 Dispose 方法。通过这种工具,您可以用编程的方式显式释放对象,而无需等待垃圾收集器来将其清除。 .NET 组件知道如何跨越多个应用程序域对本身进行序列化。这个过程可以用两种方式进行:通过引用或通过值。这两种方式的基本功能内置在 MarshalByRefComponentMarshalByValueComponent 中。实际上,.NET 组件类实现了 IDisposable,但直接或间接继承了上述两个类中的一个。 应用程序域是一种轻量型的进程。通过引用来封送对象意味着,将创建一个 proxy/stub 实体对来进行远程调用。而通过值封送对象则意味着,该对象的序列化副本将跨越域的边界发送。 控件是更特殊化的对象,它也为用户界面元素提供。当然,一个控件总是一个组件,但反之不一定成立。

Dino Esposito 供职于 Wintellect

他承担 ADO.NET 和 ASP.NET 方面的培训和咨询工作。他是 VB-2-The-Max 的创始人之一,并向 MSDN Magazine 的 Cutting Edge 专栏投稿。如果希望与 Dino 联系,可发送电子邮件至 dinoe@wintellect.com

 
http://www.cnblogs.com/james.wong/articles/93965.html