先说一下项目的背景,以前曾经做过一个项目,根据Excel中的数据批量的到网页上抓取数据,将抓取到的数据批量的回填到Excel中。这个Excel中有很多行的记录(多的时候会有好几千行),每一行数据存储能在网页上查询唯一的一条数据的条件。操作网页部分使用了微软MSHTML,在这里不做多余的介绍。这里主要讲的是如何获得最后的查询结果,并把结果填写回Excel的部分。

这个听起来好像很简单,最简单的方法就是在网页中没查询出一条数据,就将该数据存储到内存中的一个List或DataTable中,等执行完成后,从内存中获取数据填写到Excel中。但这有一个问题,在执行的过程中系统可能由于升级被迫重启。而且执行的过程通常比较长,用户不可能一直盯着屏幕看,所以如果将数据存储到内存中,容易在最后丢失先前的执行结果。这样虽然用户可以重新执行数据抓取的过程,但那又要浪费很多的时间来执行,有的时候甚至可能让用户错过任务的Deadline!我的实现方案是每次将获取的数据存储到数据库中,在数据库中记录查询的条件和查询的结果,在最后执行完成后批量的从数据库中获取数据,同步到Excel中。这样既可以在最大程度上保证获取的结果不丢失,又可以在最大限度的保证系统的性能(相比较与每次将结果直接写入到Excel中)。

那如何从数据库中获取数据呢?最简单的方法就是循环Excel中的每条数据,到数据库中获取记录,但很显然这样的效率非常低,需要多次访问数据库。在这种方法的基础上可以更近一步,就是在最后动态拼出一个Sql,类似于

select * from 查询结果表
where 查询条件 in ('查询条件1','查询条件2'...)

但是有一个问题,很多数据库(SqlServer、Oracle)在In中可以使用的条件数都有一个限制,都是1000条,其它的数据库可能也有一些类似的限制。那么一旦Excel中的数据超过1000条,该sql就会报错。所以要想使用该方法,就要根据Excel数据的行数,对数据动态的分组,每1000条为1组。然后循环每一组,根据每一组数据动态拼出一个sql,提交到数据库,最后将查询的结果合并。举一个具体的例子吧,假设有1005条数据,那么就需要将数据分成2组,第1~1000条为一组,第1001到1005为一组,将这2组数据分别到数据库中查询。如果仅仅是这样到也不是不能接受,但在获取的过程中还要考虑另外一个问题,那就是有些数据可能在数据库中没有对应的记录,这个时候要向用户做出提示。之所以会出现这种情况的原因是,从网页获取数据的时候,网页可能会出现各种问题,从而导致数据抓取失败。用户必须手动的将网页复位,并将之前获取过的数据从Excel删除,再重新开始执行才可以继续,这样就会有删错的风险,就可能在最后同步数据的时候出现数据库中没有Excel对应数据的情况。

如果按照这种方法来实现,那么最后的实现大概类似于以下代码

var 分组数=Math.Cell(Excel数据数/1000);

for(int i=;i<分组数;i++)
{
List<string> 分组数据=获得分组的数据;
根据分组数据拼sql;
执行sql
根据分组数据查询结果集
如果在结果集中没有查询到数据,将错误信息记录到Excel中
将获取数据同步到Excel中 }

这样做的结果把循环Excel获取数据的逻辑和同步Excel(实际同步Excel的代码还是比较复杂的,这里只是为了简化问题,而让这部分逻辑看起来比较简单)的逻辑混合到了一起,不但代码看上去比较乱,而且也不容易对这一段代码做单元测试。要想解决这个问题,就是把循环逻辑分离出来,23种设计模式刚好有一种可以解决这个问题,那就是迭代器模式。这个模式的标准实现方式如下

这个我就不介绍了,网上有很多的介绍,但实现这个模式需要多个类的协作,虽然可以实现逻辑的分离,但开发量还是比较大的。在实际项目中我使用了该模式在.net下的一个变体,利用了.net的yield关键字来实现迭代器。代码大致如下

/// <summary>
/// 获得迭代器
/// </summary>
/// <param name="dataSource"></param>
/// <param name="batchLoadSize">批量加载大小</param>
/// <param name="loadDataCallBack">加载数据回调,第一个参数为要加载的数据源,返回实际数据</param>
/// <param name="getTargetDataBySourceDataCallBack">根据数据源获得已经加载完成的对象</param>
/// <returns></returns>
public IEnumerable<KeyValuePair<TSource,TTarget>> GetEnumerable(IList<TSource> dataSource, int batchLoadSize, Func<IList<TSource>, IList<TTarget>> loadDataCallBack,Func<TSource,IList<TTarget>,TTarget> getTargetDataBySourceDataCallBack)
{
int beginIndex = ;
while (true)
{
List<TSource> loadSourceData = new List<TSource>();
int i;
//循环获取要批量加载的数据
for( i=;i<batchLoadSize&&i+beginIndex<dataSource.Count;i++)
{
loadSourceData.Add(dataSource[beginIndex+i]);
}
//从数据库中加载数据
IList<TTarget> targetDataList = loadDataCallBack(loadSourceData);
//获得一个源数据与目标数据的键值对
foreach (var source in loadSourceData)
{
TTarget target= getTargetDataBySourceDataCallBack(source, targetDataList);
KeyValuePair<TSource, TTarget> sourceTargetKeyPair = new KeyValuePair<TSource, TTarget>(source,target);
yield return sourceTargetKeyPair;
} beginIndex += i;
//如果已经循环到最后一组,退出循环
if (beginIndex>=dataSource.Count)
{
break;
}
}
}

对该迭代器的调用方法如下

foreach(Excel数据与查询结果 in new BatchLoadEnumerable<TSource,TTarget>().GetEnumerable(Excel数据,,加载数据的委托,根据Excel数据从加载结果中查询数据的委托))
{
if(Excel数据与查询结果.查询结果==null)
{记录查询错误
continue;
}
同步数据
}

使用该方法后,循环的逻辑与同步数据的完全分离,不但代码看上去更近简洁,而且也更容易做单元测试。我对该迭代器做了通用化的处理,只要更换加载数据与根据原始数据在结果集中查询数据的委托就可以在不同的业务场景中使用,希望也会对大家有所帮助。

使用迭代器模式批量获得数据(C#实现)的更多相关文章

  1. 深入浅出设计模式——迭代器模式(Iterator Pattern)

    模式动机 一个聚合对象,如一个列表(List)或者一个集合(Set),应该提供一种方法来让别人可以访问它的元素,而又不需要暴露它的内部结构.针对不同的需要,可能还要以不同的方式遍历整个聚合对象,但是我 ...

  2. iOS开发-迭代器模式

    迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示.开发过程中,我们可能需要针对不同的需求,可能需要以不同的方式来遍历整个整合对象,但是我们不希望 ...

  3. Javascript设计模式之我见:迭代器模式

    大家好!本文介绍迭代器模式及其在Javascript中的应用. 模式介绍 定义 提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示. 类图及说明 Iterator抽象迭代器 抽象迭代器负 ...

  4. 【设计模式 - 16】之迭代器模式(Iterator)

    1      模式简介 迭代器模式是JAVA中非常常用的模式,List.Map.Set等常见集合中都封装了迭代器Iterator. 迭代器模式的介绍: 迭代器模式用于顺序访问集合对象中的元素,而不需要 ...

  5. 设计模式(十五):Iterator迭代器模式 -- 行为型模式

    1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的有用方法,但通常你会处理一组对象或者集合. 集合不一定是均一的.图形用 ...

  6. 设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型)

      设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型) 1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的 ...

  7. Head First设计模式之迭代器模式

    一.定义 提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示: 主要解决:不同的方式来遍历整个整合对象. 何时使用:遍历一个聚合对象. 如何解决:把在元素之间游走的责任交给迭代 ...

  8. Java进阶篇设计模式之九----- 解释器模式和迭代器模式

    前言 在上一篇中我们学习了行为型模式的责任链模式(Chain of Responsibility Pattern)和命令模式(Command Pattern).本篇则来学习下行为型模式的两个模式, 解 ...

  9. 设计模式之迭代器模式——Java语言描述

    迭代器模式是Java和.NET编程环境中非常常用的设计模式.这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示 介绍 意图 提供一种方法顺序访问一个聚合对象中各个元素,无需暴露该对象的内 ...

随机推荐

  1. 关于如何提高SRAM存储器的新方法

    SRAM是当今处理器上最普遍的内存.当芯片制造商宣布他们已经成功地将更多的电路封装到芯片上时,通常是较小的晶体管引起了人们的注意.但是连接晶体管形成电路的互连也必须收缩.IMEC的研究人员提出了一个方 ...

  2. 高性能可扩展mysql 笔记(三)Hash分区、RANGE分区、LIST分区

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.MySQL分区表操作 1.定义:数据库表分区是数据库基本设计规范之一,分区表在物理上表现为多个文件, ...

  3. Java 蓝桥杯 算法训练 字符串的展开 (JAVA语言实现)

    ** 算法训练 字符串的展开 ** 题目: 在初赛普及组的"阅读程序写结果"的问题中,我们曾给出一个字符串展开的例子:如果在输入的字符串中,含有类似于"d-h" ...

  4. Java实现 LeetCode 561 数组拆分 I(通过排序算法改写PS:难搞)

    561. 数组拆分 I 给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), -, (an, bn) ,使得从1 到 n 的 min(ai, bi ...

  5. Java实现 蓝桥杯VIP 算法训练 递归求二进制表示位数

    问题描述 给定一个十进制整数,返回其对应的二进制数的位数.例如,输入十进制数9,其对应的二进制数是1001,因此位数是4. 样例输入 一个满足题目要求的输入范例. 9 样例输出 与上面的样例输入对应的 ...

  6. Java实现 蓝桥杯VIP 算法提高 最长字符序列

    算法提高 最长字符序列 时间限制:1.0s 内存限制:256.0MB 最长字符序列 问题描述 设x(i), y(i), z(i)表示单个字符,则X={x(1)x(2)--x(m)},Y={y(1)y( ...

  7. 【python】【开源】使用Tkinter和matplotlib实时显示图像,打造属于自己的性能测试小工具

    在腾讯的perfdog工具还未公开时,当时需要查看内存使用情况等信息,就用python写了个小工具 为了提升开发效率,就直接借用了雷子开源的性能测试工具的布局,自己美化了一下,然后加入了实时显示数据的 ...

  8. 运用Navicat for MySQL进行MSSQL数据转移MYSQL

    当前不同数据库进行数据转移已经不是一件麻烦事情,特别是有很多很方便的工具,而最近我在搜集各种数据时候,也需要进行大量的数据转移,并且数据库和所转移的数据库表都不同,这次给大家介绍个最简单的方法,就是使 ...

  9. 简谈Java语言的继承

    Java语言的继承 这里简谈Java语言的三大特性之二——继承. Java语言的三大特性是循序渐进的.是有顺序性的,应该按照封装-->继承-->多态这样的顺序依次学习 继承的定义 百度百科 ...

  10. spring-framework 源码的下载与构建

    整体流程: 本地环境准备 找到源码地址并拉取到本地 根据IDE工具查看源码中提供的不同的安装说明并具体操作 构建会出现的的错误及解决 一.本地环境准备 spring-framework 源码使用gra ...