写点什么

Silverlight 轻量级查询对比

  • 2011-06-21
  • 本文字数:5174 字

    阅读完需:约 17 分钟

信息系统的本质是对信息的输入、查询、计算、存储和输出操作,这就像金融的本质是价值的跨时空交换一样,虽然信息系统有各种各样的“衍生品”但都离不开信息,离不开信息的表现形式:结构化与非结构化数据,而结构化数据几乎已经成为了上世纪 90 年代以来信息系统的必需品,譬如数据库对象、XML 标签化数据、实体类对象,我们都可以认为是结构化数据。特别是在企业级应用中对结构化数据的处理能力往往是技术选型的关键指标,本期我们就来深入探讨一下 Silverlight 的数据查询能力。

笔者在《Silverlight 之重》一文中曾提到Silverlight 集成了LINQ 等高级查询语言,具有多样化的数据处理与查询方式,使得开发者在数据处理上有更多选择。下面我们就通过一个案例来了解一下Silverlight 在客户端强大的数据处理能力。

案例演示地址: http://space.silverlightchina.net/xpeter/Demo/SLQueryTest.html

源代码地址: http://space.silverlightchina.net/xpeter/Demo/code/SLQueryTest.rar

案例需求描述与分析

案例需求:“在大量的实体对象中找出满足条件的对象”。

这个需求描述只有一句话,很像现实项目中需求提出者言简意赅的口吻。但在这句话背后需要程序设计人员做的事情很多,首先需要构造出大量的实体对象数据,这需要我们建立承接结构化数据的是实体类,再通过数据生成器类来完成大量数据的产生工作;其次我们需要设计并实现 Silverlight 的查询类,最后我们需要将结果集输出到页面上。通过提炼名词,我们设计出如下类图:

在程序启动后 App 类构造了一个 MainPage 实例,同时自动构造 10 万条客户数据,数据构造完成后,用户可以通过各种查询方式查找满足条件的客户记录,并显示在“查询结果”的 DataGrid 控件中,查询耗时信息将展现在“查询效率”文本框中(在本案例中,查询条件默认为客户姓名包含某字符串的方式)。

实体类与产生器

实体类 TestModel 是按客户模型进行设计,包括姓名、性别、生日、年龄等属性。

数据产生器类 DataGenerator 类通过 CreateByCnt方法产生数据。数据产生器在企业级开发中常常用在大数据量性能测试与渗透测试中,本案例主要使用它来模拟真实的客户数据。

查询方式实现类

为了达到组件化复用和界面与逻辑分离的目的,笔者将 Silverlight 各种查询方式的具体实现放在 QueryWorker 类中,下面是 Silverlight 在客户端适用的几种轻量级查询方式:

  1. 直接查询法
    通过 For 或者 Foreach 的循环直接查找对象集合,找出满足姓名包含传入字符要求的记录,并将其添加到结果集。该方法适用于大多数在开发时明确查找条件的应用,本案例中实现方法 DirectQuery 的代码如下:
复制代码
publicvoid DirectQuery(string querystr, List<TestModel> data, refList<object> result){
foreach (var t in data)
{
if (t.Name.Contains(querystr))
{
result.Add(t);
}
}
// 通知完成查找
InvokeQueryComplete(EventArgs.Empty);
}
  1. 反射查询法
    当查找条件中实体属性需要在运行时才能确定时,直接查找法就显得不够灵活了,这就需要通过 C#反射方法获得运行时指定的实体属性信息 PropertyInfo,再通过 GetValue 方法检查该属性值是否满足包含传入字符要求。本案例中实现方法 ReflectQuery 的代码如下:
复制代码
publicvoid ReflectQuery(string querystr, List<TestModel> data, refList<object> result)
{
// 获取指定属性信息
PropertyInfo vPropertyInfo = typeof(TestModel).GetProperty("Name");
foreach (var t in data)
{
// 判断属性值是否满足查找条件
if (vPropertyInfo.GetValue(t, null).ToString().Contains(querystr))
{
result.Add(t);
}
}
// 通知完成查找
InvokeQueryComplete(EventArgs.Empty);
}
  1. LINQ 查询法
    LINQ 是.Net 框架下特有的声明式语言,开发者可以通过这种类似 SQL 的语言快速构建数据逻辑,而避免了原有面向对象操作中的复杂过程,笔者认为 LINQ 语言的表达式分为三个层次:第一层次是与 SQL 类似的 LINQ 表达式;第二层次为 Lambda 表达式;第三层次是基于 Expression 类的表达式树,这是 LINQ 的最里层,也是 LINQ 实现动态的核心。本案例中实现方法 LinqQuery 的代码如下:
复制代码
publicvoid LinqQuery(string querystr, List<TestModel> data, refList<object> result)
{
// 定义延迟执行的 Linq 查询表达式
var linqquery = from t in data
where t.Name.Contains(querystr)
select t;
//ToList 方法使得查询被执行,从而获得结果
result = linqquery.Cast<object>().ToList();
// 通知完成查找
InvokeQueryComplete(EventArgs.Empty);
}

如果使用 Lambda 表达式,可以写成 data.Where(t => t.Name.Contains(querystr)) 的筛选条件,与直接写查询表达式的含义一致。后面要介绍的两种查询方法实际都是 LINQ 的不同实现方式,只是在灵活性上要比直接写表达式更胜一筹。

  1. 表达式树查询法
    相较于 LINQ 查询法,表达式树查询更为复杂但灵活性更强。表达式树可以在运行时动态构建查询语句,它也是动态 LINQ 的实现基础,下面我就来看一下本案例中表达式树查询实现方法 ExpressionQuery 的代码:
复制代码
publicvoid ExpressionQuery(string querystr, List<TestModel> data, refList<object> result)
{
IQueryable<TestModel> custs = data.AsQueryable();
// 构造参数表达式 it
ParameterExpression it = Expression.Parameter(typeof(TestModel), "it");
// 构造待筛选字符 querystr 的常量表达式
Expression funparam = Expression.Constant(querystr);
// 获得 it 参数的 Name 属性信息,实现 Lambda 表达式:it.Name
Expression name = Expression.Property(it, typeof(TestModel).GetProperty("Name"));
// 获取字符串类的 Contains 方法信息
MethodInfo containsfun = typeof(string).GetMethod("Contains", newType[1] { typeof(string) });
// 调用 Contains 方法,实现 Lambda 表达式:it.Name.Contains(querystr)
Expression filter = Expression.Call(name, containsfun, newExpression[1] { funparam });
// 构造 Lambda 表达式:it=>it.Name.Contains(querystr)
Expression pred = Expression.Lambda(filter, it);
// 调用 Where 方法,实现 Lambda 表达式:custs.Where(it=>it.Name.Contains(querystr))
Expression expr = Expression.Call(typeof(Queryable), "Where", newType[] { typeof(TestModel) }, Expression.Constant(custs), pred);
// 形成延迟查询接口 query
IQueryable<TestModel> query = custs.Provider.CreateQuery<TestModel>(expr);
// 调用查询接口的 GetEnumerator 方法获得迭代器
IEnumerator Enumerator = query.GetEnumerator();
// 调用迭代器的 MoveNext 方法执行查询结果
while (Enumerator.MoveNext())
{
var o = Enumerator.Current;
result.Add(o);
}
// 通知完成查找
InvokeQueryComplete(EventArgs.Empty);
}

这里可能会引起读者的疑惑,为什么只需要一行代码就可以完成的查询要用这么复杂的方式去编写?再回顾一次代码其实不难发现查询所用到的参数、属性和方法都是以字符串形式“传入”表达式的,正是利用这一点就可以实现运行时动态查询。实际上,表达式定义语句的执行是在运行时才完成的,这就相当于把编码过程后置到运行时,类似于 JavaScript 的 Eval 方法是在运行时再次调用一次解释器对传入字符串进行解释执行一样,这就是在运行时实现动态的关键。

  1. 动态 LINQ 查询法

其实要实现动态查询并不需要开发人员去设计复杂的表达式树,从.Net 3.5 起,微软就提供动态 LINQ 查询类库 Dynamic Query Library。通过这个类库开发者可以将字符串形式的 Lambda 表达式作为参数传入 Where 方法,当然这样做的代价是传入字符串的语法安全风险。正因如此,微软并没有将动态 LINQ 查询类库预置到 Silverlight 基础类库中。考虑到这一点对现实开发的意义重大,笔者自行封装了动态 LINQ 框架,并提供了带有智能感知功能的动态 LINQ 语句输入框,这样就允许用户在 Silverlight 应用的运行时编写 LINQ 查询语句,并动态执行查询。

本案例中动态 LINQ 的实现方法 DynamicLinqQuery 代码如下:

复制代码
publicvoid DynamicLinqQuery(string querystr, List<TestModel> data, refList<object> result)
{
try
{
var qtms = data.AsQueryable();
// 将传入查询语句直接传给 Where 方法
var dynamicquery = qtms.Where(querystr);
result = dynamicquery.Cast<object>().ToList();
}
catch (Exception e)// 动态 Where 子句存在语法风险
{
// 通知查找出错
_errorMessage = " 你的查询语句遇到以下问题:" + e.Message;
InvokeQueryError(EventArgs.Empty);
}
// 通知完成查找
InvokeQueryComplete(EventArgs.Empty);
}

现在查询类的所有方法都已经准备好,现在需要在 UI 层调用查询方法并展现查询结果了。

查询调用与结果反馈

由于所有查询方法都统一了入参结构,所以在调用上几乎都是相同的,这里笔者就以动态 LINQ 查询为例介绍 UI 层的查询调用与结果反馈的代码实现。

本案例中 UI 层的 MainPage 页面承担了所有交互任务,该页面包含一个查找类的实例 queryworker。由于查找类在完成查找任务后异步触发完成事件,因此在构造 MainPage 页面时需要委托 queryworker 的查询完成事件,代码如下:

复制代码
queryworker.OnQueryComplete += (sender1, e1) => Dispatcher.BeginInvoke(queryworker_OnQueryComplete);

在页面加载完成后查询按钮点击事件将统一委托给 DoQuery 方法:

复制代码
btnDLinq.Click += newRoutedEventHandler(DoQuery);

在 DoQuery 方法中将根据不同的按钮,调用不同的查询方法。其中,动态 LINQ 查询的调用代码如下:

复制代码
querystr = isiQuery.Words;
queryworker.DynamicLinqQuery(querystr, testdata, ref queryresults);

接下来 queryworker 实例在完成动态查询后就会触发通知事件来回调上文中的接收方法 queryworker_OnQueryComplete。该方法负责记录耗时信息,并将结果显示在 DataGrid 控件中,其代码如下:

复制代码
void queryworker_OnQueryComplete()
{
long usetime = Environment.TickCount - StartTickCount;
tbr.Text = "->" + string.Format(" 在{0}万条数据中,{1}找到{2}条记录, 用时:{3}毫秒\n", testdata.Count / 10000.0, currquery, queryresults.Count, usetime) + tbr.Text.Replace("->", "");
tbRsCnt.Text = string.Format(" 共{0}条记录 ", queryresults.Count);
dg.ItemsSource = queryresults;
}

虽然在 DataGrid 数据绑定中只需要指定数据源,但在页面定义中还需要对数据进行翻译处理。比如:本案例中的性别都是以代码形式存在于数据实例中,但在 DataGrid 中却显示为图片,这正是通过 Silverlight 提供的模板列 DataGridTemplateColumn 和绑定数据中的转换器 Converter 来实现的,这里笔者不再展开介绍,读者可以自行研究本案例源代码。

Silverlight 查询性能对比

上图是百万条数据在相同的查询条件下的性能对比,测试平台是普通的 2G 双核笔记本,操作系统为 Win7,浏览器为 IE9。从结果来看,除了效率比较低下的反射方式,其他查询方法均在 0.3 秒内完成对百万数据的查询,性能相当不错。而其中表达式树查询法仅用时 218 毫秒,最为高效。这正是因为表达式树已经是 LINQ 最核心的层次,不需要过多转换,所以在执行上更快。更为可喜的是即便使用灵活性最强的动态 LINQ 在性能损耗上也并不明显,所以在 Silverlight 企业级应用中使用动态 LINQ 是完全可行的。事实上,笔者在目前开发的项目中经常使用动态 LINQ 来实现复杂的查询,甚至是规则引擎。

Silverlight 查询性能展望

前不久的 Mix11 大会上,微软已经展现了 Silverlight5 强大的 3D 渲染能力和更加丰富的商业应用支持,但并没有表示对 LINQ 的扩展。其实更多的 Silverlight 企业级应用开发者希望能够在未来的版本中加入类似 PLINQ 的并行框架,这样就可以利用并行进一步提升客户端的查询效率。笔者相信随着 Windows Phone 7、Windows 8 这些微软核心竞争力产品采用 Silverlight 作为“瘦 UI”的展现层技术,并行框架被放进 Silverlight 基础框架的日子不会太远。

本期通过一个实例向大家介绍了 Silverlight 轻量级查询的实际性能,希望对正在采用或者将要采用 Silverlight 的项目开发者有所帮助。

如果您对该作者《Silverlight 轻舞飞扬》系列感兴趣,你可以点击下面的链接: http://www.infoq.com/cn/silverlight-column


感谢张凯峰对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2011-06-21 00:003189

评论

发布
暂无评论
发现更多内容

蓝易云 - 用云服务器搭建网站怎么弄的

百度搜索:蓝易云

云计算 Linux 运维 搭建网站 搭建网站云服务器

数字样机:飞行器状态控制系统仿真

DevOps和数字孪生

数字样机

LED租赁屏市场

Dylan

LED LED display LED显示屏 市场 舞台表演

硅纪元视角 | Stability AI推出Stable Video 4D,40秒生成8角度动态视频!

硅纪元

Stable Video 4D Mistral Large 2 “Frame”眼镜 HoloDreamer

得物App弱网诊断探索之路

得物技术

ios android 企业号2024年7月PK榜

崖山异构数据库迁移利器YMP初体验-Oracle迁移YashanDB

YashanDB

yashandb 崖山数据库 崖山DB

从0到1:理发店预约剪发小程序开发笔记(上)

CC同学

极盾故事|某农商行数据安全制度和数据分类分级建设

极盾科技

数据安全 数据分类分级

数据恢复easyrecovery是磁盘工具吗 easyrecovery数据恢复软件怎么用

阿拉灯神丁

磁盘 EasyRecovery 数据恢复软件 硬盘数据恢复 软件包

大庆正规等保测评机构有几家?在哪里?

行云管家

等保 等保测评 大庆

蓝易云 - Ubuntu下安装nginx服务,实现通过URL读取ubuntu下图片

百度搜索:蓝易云

nginx Linux ubuntu 运维 云服务器

蓝易云 - Linux:工具(vim,gcc/g++,make/Makefile,yum,git,gdb)

百度搜索:蓝易云

云计算 Linux 运维 yum 云服务器

参加可观测性Observability Foundation认证培训,您有哪些收益?

雅菲奥朗

可观测性 可观测性认证

蓝易云 - 免备案服务器托管是啥意思呀

百度搜索:蓝易云

云计算 运维 服务器 云服务器 高防服务器

深度解读GaussDB(for MySQL)与MySQL的COUNT查询并行优化策略

华为云开发者联盟

MySQL 数据库 华为云开发者联盟 企业号2024年7月PK榜

获取闲鱼商品详情api

api开发

电信行业怎么定义?需要采购堡垒机吗?

行云管家

网络安全 数据安全 堡垒机 电信

开发者必备:淘宝商品列表接口集成全攻略

tbapi

淘宝API接口 淘宝商品列表数据接口 淘宝商品列表数据采集

解读阿里云搜索开发工作台如何快速搭建AI语义搜索及RAG链路

阿里云大数据AI技术

人工智能 自然语言处理 大模型 rag

文献解读-临床试验-第二十二期|《新抗原负荷作为中国非小细胞肺癌II/III患者的预后和预测标志物》

INSVAST

基因测序 基因数据分析 临床试验

人工智能|ReACT 推理提示

测吧(北京)科技有限公司

测试

技术路线速通!用飞桨让京剧人物照片动起来

百度Geek说

百度飞桨 企业号2024年7月PK榜

最新资讯!2024可信云大会重磅发布《可观测性能力建设指南》!

乘云数字DataBuff

白皮书 可观测平台 可信云大会

和鲸科技参与第17届中国R会议,分享有组织科研的模型生命周期管理

ModelWhale

人工智能 R 数据科学 AI4S

蓝易云 - Ceph入门到精通-sysctl参数优化

百度搜索:蓝易云

云计算 运维 Ceph 云服务器 高防服务器

极限科技闪耀 2024 可信数据库发展大会,多款自研产品引领搜索技术新纪元

极限实验室

信通院 极限科技 数据库发展大会

即时战略游戏:帝国时代2 for Mac 3.3.1769 中文移植版

你的猪会飞吗

mac软件下载 mac单机游戏

PHP 与淘宝详情 API 的融合:构建智能电商应用

api开发

ETL数据集成丨将PostgreSQL数据库数据实时同步至PostgreSQL

RestCloud

postgresql 数据同步 ETL 数据集成平台 数据库同步

AI 应用实战营 - 作业 八 - Coze 制作 Bot - 2

德拉古蒂洛维奇

华为云Serverless可观测性解决方案打造高效、可靠的云原生应用

华为云开发者联盟

云原生 可观测 华为云开发者联盟 企业号2024年7月PK榜

Silverlight轻量级查询对比_Java_吴磊_InfoQ精选文章