红帽白皮书新鲜出炉!点击获取,让你的云战略更胜一筹! 了解详情
写点什么

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:003038

评论

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

一文搞懂Vue的MVVM模式与双向绑定

EquatorCoco

Java vue.js Vue

1688API接口推荐:1688工厂档案信息数据接口

tbapi

1688 1688API接口 1688工厂档案信息

ADB 下载、安装及使用教程:让你更好地管理 Android 设备

霍格沃兹测试开发学社

游戏发行新方式——游戏海外发行代理

Ogcloud

游戏 云游戏 游戏发行 游戏发行公司

程序员来看,你选哪一门编程语言提升自己?

高端章鱼哥

HStream Webinar: 兼容 Kafka 协议的下一代流数据平台

EMQ映云科技

kafka mqtt

干货盘点!市场调查与分析必备的10个模板!

彭宏豪95

职场 在线白板 行业调研 效率软件 调研报告

图数据库基准测试 LDBC SNB 系列讲解:Schema 和数据生成的机制

不在线第一只蜗牛

数据库 LDBC

探索自然语言处理:语言模型的发展与应用

霍格沃兹测试开发学社

读取速率十倍于 Elasticsearch,TDengine 在知轮科技智慧轮胎系统中的应用

TDengine

设计原则 — DRY & Rule of three

Lemoon Can

设计原则 DRY Rule Of three

交易所量化趋势跟单自动交易策略系统开发

西安链酷科技

合约跟单 量化交易

数字货币钱包系统app开发公司

西安链酷科技

数字货币钱包开发 区块链钱包开发 公链钱包开发

用Python编写自己的微型Redis

快乐非自愿限量之名

Python redis

华为校园鸿蒙公开课走进南京大学

Geek_2d6073

走进AI新时代:织信低代码的实践与启示

优秀

AI 低代码 AI智能

一文告诉你服务器为什么要托管?

Finovy Cloud

云服务器 IDC 服务器托管

“打工人”的自我修养-如何在30秒内把“问题”讲清楚

K

原创 职场 认知 逻辑

SQL中如何添加数据:基础指南

霍格沃兹测试开发学社

数字钱包app开发、web3钱包系统搭建

西安链酷科技

区块链钱包开发

TikTok直播畅通无阻,海外直播专线打造稳定流畅的网络环境

Ogcloud

TikTok 海外直播专线 海外直播 tiktok直播 tiktok直播专线

与鲸同行,智领未来!和鲸科技高校市场渠道合作伙伴正式开启招募

ModelWhale

人工智能 数据科学 渠道

提升跨境直播体验,选择适用的直播专线

Ogcloud

海外直播专线 海外直播 跨境直播 跨境直播专线

活动预告:如何培养高质量应用型医学人才?

ModelWhale

数据分析 人才培养 R语言 临床医学 新医科

现货量化机器人/合约交易/合约跟单交易所系统开发(方案项目)

西安链酷科技

量化交易系统 ai跟单系统

开源LLM大模型聊天

百度开发者中心

人工智能 大模型 问答助手

区块链加密货币数字钱包开发公司

西安链酷科技

钱包系统开发 公链钱包

深入了解 Linux 常用性能统计命令

霍格沃兹测试开发学社

阿里通义灵码全面公测,来看看它的水平怎么样?

阿里巴巴云原生

阿里云 云原生 通义灵码

探索机器学习:从基础概念到应用实践

霍格沃兹测试开发学社

新零售SaaS架构:什么是线上商城系统?

快乐非自愿限量之名

架构 零售 SaaS

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