使用 WCF 和自定义 DataGrid 在 Silverlight 中处理大型数据集






4.57/5 (7投票s)
使用基于 WCF 的服务和自定义 DataGrid 在 Silverlight 中显示、编辑和保存大型数据集
引言
本文简要介绍了上传和下载大型数据集及其进行编辑的高级技术。为此,仅使用了 Silverlight 3 随附的标准库。通信基于 WCF 服务,UI 只使用数据网格、按钮和文本框。本文的有趣之处在于,它向您展示了如何通过使用扩展类、反射和一些简单但功能强大的技巧来克服可能遇到的常见障碍。
背景
当我开始使用 Silverlight 时,我快速浏览了一些示例并很快构建了第一个示例应用程序。我在数据绑定和 XAML 方面遇到了一些小问题,但在理解了基本概念后,一切似乎都变得容易了。我找到了许多涵盖基本概念的优秀示例,它们为我打下了良好的基础,让事情看起来很简单。然后,我不得不实现第一个实际应用程序,事情开始变得棘手。我想实现的看似并不难——如果您在 WinForms 中实现它——但在 Silverlight 中,我很快就发现它不像在 WinForms/MFC 中那样工作。以下是应用程序应执行的操作:
- 处理大量数据:上传和下载超过 50,000 行数据,数据量超过 10 MB
- 快速显示数据(高性能)
- 添加数据而不使用弹出对话框 - 只需在末尾始终保留一个空行
- 排序数据
- 过滤数据
这一切听起来很简单,但事实并非如此。第一个问题是,使用 WCF 服务上传超过 8 KB 的数据无法开箱即用。第二个问题是,向数据网格添加一个空行很容易,但结合排序功能,空行也会被排序,并且不总是在最后:如果您更改排序方向,它会变成第一行。最后,性能可能会成为一个问题。使用未修改的数据网格对 50,000 行数据进行排序需要几秒钟,这绝对是无法令人满意的,尤其是与 WinForms 相比。
本解决方案概述
基本上,本解决方案包含两部分:
- 用于处理大型数据集的 WCF 服务
- 数据网格的扩展,用于显示、编辑、排序和过滤数据

首先,我们将查看 WCF 服务,以便有一些数据可供操作。然后,我们将查看数据网格。此演示使用 Silverlight 3.0、.NET 3.5,并使用 Visual Studio 2008 编写。只需打开解决方案并按 F5 即可运行。我不会详细介绍如何部署它(客户端和服务器在不同机器上),也不会过多深入介绍如何使用 Visual Studio 2008 定义服务、XAML 文件等。已经有很多非常好的示例——包括 CodeProject 上的——所以我认为我无法做得更好或为这些示例添加任何内容。
WCF服务
WCF 服务在服务器端实现,是“MappingDataEditor.Web
”项目的一部分。加载大量数据(几乎)可以开箱即用。在此示例中,将“test_data.csv”(位于子目录“TestData”中)的内容加载 50 次。该文件有 1,000 行,因此加载了 50,000 行,从服务器发送到客户端的数据量约为 11 MB。在“web.config”中,确保设置“dataContractSerializer
”参数。
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
上传稍微复杂一些,因为您一次调用无法上传超过 8 KB 的数据。因此,数据会分块传输。客户端由 UploadState
对象管理。服务器端所需的函数如下所示:
[OperationContract]
Guid GetHandle(string pTableame);
[OperationContract]
void AddChunk(Guid pHandle, string pBlock);
[OperationContract]
bool Complete(Guid pHandle);
数据通过以下步骤传输:
- 表中的数据被序列化。
- 初始化
UploadState
对象,调用StartUpload
后,通信完全由UploadState
对象处理。 - 使用 WCF 调用“
GetHandle
”将第一个块发送到服务器。 - 然后使用 8KB 的块通过多次调用“
PushOneBlock
”来传输数据。 - 每次块传输后,都会通知调用进程进度(您可以订阅“
ProgressChanged
”事件)。 - 传输完最后一个块后,将调用“
Complete
”函数。此时,服务器端服务知道所有数据都已传输完毕,并可以执行任何需要对数据执行的操作(在本例中为保存数据)。
数据网格
数据网格使用 SortCollectionView
对象,该对象实现了 ICollectionView
和 IList
接口。
public class SortCollectionView<T> : ICollectionView, IList<T>
{
private ObservableCollection<T> myContent = new ObservableCollection<T>();
...
这样,我们就可以自己实现排序和过滤。通过这种方式,我们可以确保用于输入新数据的空行始终位于末尾。我们只需删除最后一行,对数据进行排序,然后再次添加空行。
object selectedItem = myDGTable1.SelectedItem;
SortDescription sortDesc = myReturnTableView.SortDescriptions[0];
this.myReturnTableView.Content.RemoveAt(this.myReturnTableView.Content.Count - 1);
if (myChGeneric.IsChecked == true)
{
myReturnTableView.Content.SortQuick(new GenericComparer<ReturnTableEntry>
(sortDesc.PropertyName, sortDesc.Direction == ListSortDirection.Ascending,
StringComparison.CurrentCulture, myReturnTableView[0]));
}
else
{
if (sortDesc.Direction == ListSortDirection.Ascending)
{
myReturnTableView.Content.SortQuick(new Field1ComparerUp());
}
else
{
myReturnTableView.Content.SortQuick(new Field1ComparerDown());
}
}
myDGTable1.SelectedItem = null;
myReturnTableView.Content.Add(new ReturnTableEntry());
myReturnTableView.FireCollectionChanged();
myDGTable1.SelectedItem = selectedItem;
我们还可以通过传递一个 FilterCallback
函数并将其应用于数据来实现过滤。
public IEnumerator<T> GetEnumerator()
{
if (myFilter == null)
{
return this.myContent.GetEnumerator();
}
else
{
Collection<T> col = new Collection();
foreach (T item in this.myContent)
{
if (myFilter(item))
{
col.Add(item);
}
}
return col.GetEnumerator();
}
}
Using the Code
在简要了解了此示例应用程序的有趣部分之后,下面是与文件相关的有趣列表及其用途:
- MainPage.xaml:XAML 中的 UI 定义,定义了
Tabpage
、Canvas
、Table
、Buttons
和Textboxes
的布局。 - MainPage.xaml.cs:包含大部分 UI 逻辑的代码隐藏文件。
- SortCollectionView.cs:用于对
ObservableCollection
进行排序和过滤的对象。 - ChangedSortDescriptionCollection.cs:需要访问“
CollectionChanged
”事件。 - Extensions.cs:用于对
ObservableCollection
进行排序的泛型扩展方法(尝试使用Bubblesort
并将其与Quicksort
进行比较,看看有多慢)。 - UploadState.cs:用于将大量数据作为
string
上传的辅助类。通过扩展此类的 ZIP 压缩器(可以在 此处 找到一个很棒的实现)可以加快上传速度。 - IDataService.cs:WCF 服务的接口。
- DataService.svc.cs:WCF 服务在服务器端的实现。
- DataServiceHostFactory.cs:“
ServiceHostFactory
”,用于服务,以防止在使用多个服务时出现问题。 - Default.aspx:托管 Silverlight 应用程序的网站。
- UploadManager.cs:跟踪在将数据分块上传时接收到的块。
- UploadSession.cs:单个上传会话的实现。
- UploadSessions.cs:当前所有活动“
UploadSession
”的缓存。 - ReturnTableEntry.cs:此示例中用于表示表中某一行的一个简单对象。
关注点 - 性能考虑
在评估性能时,我做出了一些非常有趣的观察。排序基于 Quicksort
算法。起初,我实现了一个泛型排序器,该排序器使用反射来确定要排序的列的数据类型。
this.myPropertyName = value;
this.myPropInfo = this.myTypeInstance.GetType().GetProperty(this.PropertyName);
if (this.myPropInfo.PropertyType.Equals(typeof(string)))
{
this.CompareFunction = this.StringComparer;
}
else if (this.myPropInfo.PropertyType.Equals(typeof(int)))
{
this.CompareFunction = this.IntComparer;
}
else if (...
根据类型,它会设置应使用的比较器。排序数据时,比较器会再次使用反射获取值。
private int StringComparer(T pTypeInstance1, T pTypeInstance2)
{
string string1 = (string)myPropInfo.GetValue(pTypeInstance1, null);
string string2 = (string)myPropInfo.GetValue(pTypeInstance2, null);
...
在 2.7 GHz 的 Core2Duo 处理器上,对表中 50,000 行数据进行排序需要 8 秒以上。但是,如果您不使用反射,而是传递一个直接访问属性的比较器,排序只需不到 1 秒,比前者快 10 倍以上。因此,反射对于实现通用解决方案非常有用,但对性能有很大影响。
第二件有趣的事情是,您第一次执行某些代码时,执行时间总是比后续调用长约 20%。这里可以看到 .NET 解释器对代码的影响。您可以通过查看应用程序的下半部分来尝试。在 textbox
中,您可以看到操作持续了多长时间。

另外,请注意 SortCollectionView
。它在内部将数据存储在 ObservableCollection
中。这样,您可以直接设置来自 WCF 服务的数据,无需任何转换。因此,设置数据并显示它仅需 0.1 秒。如果您将数据复制到其他格式,则需要花费几秒钟。
备注
这只是一个示例应用程序,在使用它之前,您应该对其进行彻底测试。此外,此实现仅适用于 ObservableCollections
和 Properties
。它不适用于 DataViews
。无论如何,我希望您喜欢它,并且它能为您节省大量时间。祝您玩得开心!
历史
- 2010 年 4 月 5 日:测试应用程序“
MappingDataEditor
”的 Silverlight 3 版本 0.1 - 2010 年 7 月 2 日:Silverlight 4.0 和 .NET 4.0 的新版本
- 修复了复选框的一个错误
- 添加了分页加载功能
- 注意:性能未针对大型数据集进行优化。请查看 TPL 和并行循环。
- 测试数据的访问路径在 Web.config 中硬编码。请编辑它,或直接将项目文件夹提取到“C:\”。
- 按下“创建”按钮将生成一个新的、大型的测试数据文件,包含 100,000 行。之后,一次加载数据可能会失败。此时只能进行分页加载。请在“DataService.svc.cs”中调整要创建的行数。