简介:面向Delphi/Rad Studio开发者的EMS Advanced Data Import 3.15.0.3完整源码控件包,专为Rad Studio 12 Athens环境设计,可帮助程序员从SQLite、MySQL、Oracle、SQL Server等主流数据源向目标数据库快速导入、迁移、备份与转换数据,并支持批量导入、智能数据类型识别,显著降低异构数据整合难度。压缩包共344个文件,总大小仅3.78MB,以97个pas核心源码、66个dproj工程文件、32个cbproj/groupproj组建工程及31个dfm窗体资源为主,另含bat构建脚本、docx说明文档与可直接运行的示例演示工程,结构清晰紧凑,既能整体编译,也能按需抽取组件进行二次开发。该控件包已吸引74人学习浏览。由于提供全量源代码,开发者既可开箱即用地调用现成导入功能,也可深入剖析内部机制,定制批量导入规则、类型映射、数据清洗等高级行为,以适应独特的业务场景;附带的示例与脚本降低了上手成本,是Delphi数据接入与数据治理项目中值得收藏的实用工具。
1. EMS Advanced Data Import for Rad Studio 12 Athens:为什么数据导入还要花钱装控件
一个日销报表功能,用户拿到的 xlsx 有合并单元格、日期、数值混排,还有中文表头。走 Delphi 自带方案操作 Excel 时,要么绕到 ADO+Jet 驱动,要么用 OLE 一个格子一个格子读——慢、依赖环境、Office 版本一变就翻车。EMS Advanced Data Import 3.15.0.3 就是解决这类问题的控件:把 Excel、CSV、JSON、XML、DBF 等文件直接读成 TDataSet,TClientDataSet 接住就能绑定 DBGrid 或落库。这次拿的是 Full Source 包,控件内部实现开放,在 Rad Studio 12 Athens(Delphi 12.3)下能自己编译、改字段映射逻辑。这篇笔记就走一遍从装包到落库的完整路线,适合受够了手工解析 xlsx 的 ZIP+XML 的 Delphi 用户。
2. 把 Full Source 包装进 Delphi 12.3:源码编译、设计期包安装与 Library Path 配置
拿到文件名里带“Full Source for Rad Studio 12 Athens”的控件包,第一反应是解压后直接找 .dpk 双击。大部分 Delphi 控件确实是这样装的,但带源码的包有一个固定套路:源码目录、包文件、示例工程摆放有规律,先把目录看明白,比急着编译更省时间。装错版本导致的报错,九成都是在这一步埋下的。
2.1 拿到带源码的控件包先看什么:目录结构与运行期/设计期包
常见做法是,解压后根目录下有 Source、Packages、Samples、Docs 这几个并列文件夹,这个结构基本是 Delphi 商业控件约定俗成的安排:
- Source:真正的 .pas 源文件。Full Source 的价值就在这里,不是只有编译好的 .dcu/.bpl,异常时可以直接步入 TADImport.pas 内部,看它逐行怎么解析文件。
- Packages:按 Delphi 版本分层的 .dpk/.dproj。Rad Studio 12 Athens 对应 Delphi 12 那一组,Win32/Win64 下通常有同名运行期包(类似 EMS.ADImport.dpk)和带 dcl 前缀的设计期包。
- Samples:官方示例工程,常见的是 VCL Demo,也有 FireMonkey 示例。示例里一般都有“导入到 DBGrid”和“导入后落库”两类用例,建议先跑 Samples 再碰业务代码。
- Docs:帮助文档或 ReadMe,里面会写明平台依赖和 BPL 依赖顺序。
在 Rad Studio 12 Athens 下装控件,版本匹配是第一优先级。3.15.0.3 是控件版本号,后面跟着的“for Rad Studio 12 Athens”说明它编译时用的是 12 的 RTL。如果本机只有 Delphi 10.4,直接打开新版包十有八九报“不支持的平台”或找不到 System.Win 之类单元。检查方式很简单:看 Packages 目录下有没有 12 对应的子目录。这一步不值钱,但很多人在这里翻车——拿旧版 IDE 开新版包,然后回头怪控件装不上。
2.2 用 msbuild 编译运行期包,再装设计期包到工具箱
常规操作是直接在 IDE 里打开运行期包 .dpk,右键 Compile 再 Install。但我更习惯命令行,因为编译输出完整,而且 64 位包在 IDE 里安装不是每次都顺。先把运行期包编译出来:
msbuild "Packages\EMS.ADImport.dproj" /t:Build /p:Config=Release /p:Platform=Win64注意,Delphi 的包工程经过 .dproj 的 msbuild 封装,这条命令本质是用 msbuild 驱动 dcc64。如果目录里只有 .dpk,IDE 加载时会自动生成对应的 .dproj,命令行编译建议直接用 .dproj,省掉 IDE 干预。机器上 msbuild 不在 PATH 时,用开始菜单里 Embarcadero 自带的“Command Prompt”打开终端再执行。编译运行期包时平台必须选 Win64,否则后面 64 位工程里根本看不到控件单元,等到编译主程序时才报“找不到 EMS.AdvancedDataImport”,那时候再回头找原因更浪费时间。
运行期包编译通过后,再编译设计期包:
msbuild "Packages\dclEMS.ADImport.dproj" /t:Build /p:Config=Release /p:Platform=Win64设计期包带 dcl 前缀,它引用运行期 BPL,安装后会在工具箱里注册 TADImport 组件。如果编译时报找不到运行期 BPL,把运行期包的输出目录加入系统 PATH,或直接把 BPL 复制到 Delphi 的 BPL 输出目录(默认是公共 BPL 目录)。全部编译结束后,打开 IDE 的 Component > Install Packages,点击 Add 手动选择设计期 BPL,或者重启 IDE 让桌面自动加载。到这里,工具箱里应该能搜到 TADImport。
提示:如果 IDE 里打开 .dpk 编译时频繁报错,优先切到命令行编译。IDE 的安装包编译跑在 32 位 bds.exe 进程里,大工程容易出现内存不足,第 5 章会专门展开这个现场。
2.3 把 Source 目录加进 IDE 搜索路径:出错时直接看 TADImport.pas
设计期包装好、控件能拖到窗体上,只算完成一半。Full Source 的真正用法是把 Source 目录加进 Delphi 的 Library Path,这样工程引用的不是编译符号,而是真实 .pas 源码,出异常时 IDE 能自动跳到控件实现行。
在 Rad Studio 12 Athens 里的路径是 Tools > Options > Environment Options > Delphi Options > Library,先把 Platform 切到 Win64,然后在 Library Path 里追加 Source 目录。注意 Win32 和 Win64 两个平台都要加,因为正式项目很可能要出 32 位安装包和 64 位安装包两个版本。加完之后 Delphi 重新解析单元依赖,第一次编译会慢一些,后续走缓存就正常了。
这步做完,自己的工程里就能按住 Ctrl 点 TADImport 相关单元名跳进源码。我平时调导入逻辑,经常直接在源码里加一行 OutputDebugString,看它内部解析到哪一行字段出问题——这个体验和看 FireMonkey 源码是同一套思路,都是带源码控件在 Delphi 12 时代最值钱的部分。后续章节里提到的属性名和参数,建议都以你安装目录里这份源码为准核对一遍,不同小版本确实有命名差异。
3. 读 TADImport 源码前先懂对象模型:ImportType、类型推断和 TDataSet 输出
控件装好后最怕照着老帖子代码抄,结果属性名对不上。EMS Advanced Data Import 从 2.x 到 3.x API 做过调整,所以这一章先把对象模型讲清楚,再给最小可跑代码,最后附参数速记。这样哪怕版本有差异,也能按图索骥在源码里找到对应 setter。
3.1 TADImport 是入口:一个组件覆盖 Excel、CSV、JSON、XML、DBF
核心组件是 TADImport,单元名一般是 EMS.AdvancedDataImport。它不是单一解析器,而是一个导入引擎总入口,用 ImportType 属性切换底层不同读取器:Excel、CSV、JSON、XML、DBF、TXT 都走同一个对象。这种设计的选型理由很直接:
- 只用 CSV 读取,开源库能应付;但一旦要读 .xlsx,就绕不开 ZIP 解压和 sheet 的 XML 解析,自己写的工作量不是几个函数能打发的。
- ADO + Jet 驱动读 Excel 在 32 位下勉强能用,目标机器没装 Access Database Engine 或系统是 64 位时立刻翻车。
- 同类第三方控件里,NativeExcel for Rad Studio 12 偏重“直接读写 Excel 文件”,FlexCel 偏重“模板生成报表”;EMS Advanced Data Import 的差异点是把“导入到数据集”做成了核心场景——它输出的是字段和记录,不是单元格坐标。
TADImport 的主要输入是 FileName 和 ImportType,输出侧常见方法有 ImportAsClientDataSet,也有填充到已有 TDataSet 的实现,还有一个 OnProgress 类回调用于大文件进度。业务层里我最喜欢返回 TClientDataSet 的方式:CDS 是内存表,可以改字段类型,可以套 DataSource 绑 UI,也可以直接做 Delta 落库,后续处理弹性最大。
3.2 类型推断是双刃剑:列类型、空行和表头怎么被解析
TADImport 内部做的事拆成三步:按 ImportType 打开文件按行流式读取;根据前几行推断每列字段类型(整数、浮点、日期、字符串);生成字段定义并创建输出数据集,逐行填充。这个流程决定了它的行为特点。
类型推断是双刃剑。默认会把“2024-01-01”识别成日期字段,把“3.14”识别成浮点,看起来省事;但脏数据场景里这种聪明会坑人。比如一列前 100 行是数字,第 101 行出现一个“N/A”,推断结果是浮点字段,字符串就放不进去,导入直接中断。这时候要靠字段映射和类型覆盖兜底,第 4 章会展开。
还有一个常见行为差异:默认忽略完全空行,但不会忽略只有空格的行;表头默认作为第一行处理,且表头文字会成为字段名来源。这些行为都挂在 HasHeader 和 StartRow 两个参数上。动手前先把这两个参数想清楚,能省一半排错时间。表头占据多行、前面有说明文字这类情况,都是靠 StartRow 往后推解决的。
3.3 最小可跑代码:把 Excel 工作表导入成 TClientDataSet
为了不被版本差异卡住,我一般用一个最小 VCL 示例验证安装:放一个按钮、一个 DBGrid、一个 TDataSource,在按钮点击里写下面这段:
uses EMS.AdvDataImport, EMS.ADImportExcel, Data.DB, Datasnap.DBClient; procedure TForm1.btnImportClick(Sender: TObject); var ADImport: TADImport; CDS: TClientDataSet; begin ADImport := TADImport.Create(nil); CDS := TClientDataSet.Create(nil); try ADImport.ImportType := itExcel; ADImport.FileName := 'D:\data\sales_2025.xlsx'; ADImport.StartRow := 1; ADImport.HasHeader := True; ADImport.TrimValues := True; CDS := ADImport.ImportAsClientDataSet; DataSource1.DataSet := CDS; DBGrid1.DataSource := DataSource1; finally ADImport.Free; end; end;逻辑说明:创建 TADImport,配置输入文件和表头参数,调用 ImportAsClientDataSet 一次性拿到内存数据集,最后交给 DataSource。TClientDataSet 这里没有调用 CreateDataSet,因为字段结构由导入引擎按文件内容生成,直接绑定即可。CDS 的生命周期由 DataSource 接管,ADImport 在 finally 里释放。
参数说明:StartRow=1 表示从第一行读,如果模板前两行是标题和说明就改成 3;HasHeader=True 时第一行被用作字段名;TrimValues 会去掉单元格首尾空格,遇到文本型数字时建议打开。这段能跑通,说明控件安装和源码编译没问题,再往后就可以研究字段映射和性能调优了。
4. 真实导入场景里必调的参数:Excel、CSV、JSON 的完整配置
最小代码能跑通后,很多人直接往业务里搬,然后在第一个“脏表格”上翻车。这一章给出工作中稳定使用的参数表和三个真实场景写法:CSV 中文编码、Excel 脏表头、JSON 嵌套路径。属性名以你安装的源码为准,思路是通用的。
4.1 参数速查表:StartRow、HasHeader、Encoding、Separator 该设成什么
下面几个参数是我在项目里按频率排列的稳定配置:
| 参数/属性 | 作用 | 常用值 | 备注 |
|---|---|---|---|
| ImportType | 选择解析器 | itExcel / itCSV / itJSON | 与文件后缀强相关 |
| FileName | 输入文件路径 | 绝对路径 | 相对路径在 IDE 和 EXE 下基准不同 |
| StartRow | 数据起始行 | 有说明文字时设为 2 或更大 | 与 HasHeader 配合使用 |
| HasHeader | 首行是否作为字段名 | True | 无人机生成文件时建议 False |
| Encoding | 文本编码 | TEncoding.UTF8 / ANSI | CSV 乱码主要看这里 |
| Separator | 列分隔符 | ';' 或 ',' | 中文 Excel 导出的 CSV 常见分号 |
| TrimValues | 去除单元格首尾空格 | True | 防止比对失败 |
| EmptyRows | 空行处理策略 | 跳过 | 表单模板常带大量空行 |
提醒一句:Encoding 并非所有版本都暴露成 TADImport 的属性,有的版本藏在 Options 里或底层读取器单元中。找不到时不要硬配,直接翻 Source 目录里 CSV reader 的单元,搜 Encoding 关键字就能找到。这就是 Full Source 包省心的地方——第三方控件文档往往滞后,源码是最新文档。
4.2 按扩展名分派导入器:Excel、CSV、JSON 的完整例子
实际项目导入源不止一种,我习惯写一个函数,根据扩展名选择 ImportType,再单独处理 CSV 的编码和分隔符:
function ImportFileToCDS(const AFileName: string): TClientDataSet; var ADImport: TADImport; Ext: string; begin ADImport := TADImport.Create(nil); try Ext := LowerCase(ExtractFileExt(AFileName)); if Ext = '.xlsx' then ADImport.ImportType := itExcel else if (Ext = '.csv') or (Ext = '.txt') then begin ADImport.ImportType := itCSV; ADImport.Encoding := TEncoding.UTF8; ADImport.Separator := ';'; end else if Ext = '.json' then ADImport.ImportType := itJSON else raise Exception.Create('不支持的导入文件类型: ' + Ext); ADImport.FileName := AFileName; ADImport.StartRow := 1; ADImport.HasHeader := True; Result := ADImport.ImportAsClientDataSet; finally ADImport.Free; end; end;这段把三种常见格式统一成入口:调用方只关心文件名,不感知底层解析。CSV 的 Separator 在中文系统里特别容易踩坑:业务方从 Excel 另存为 CSV 时,区域设置可能把分隔符写成“;”而不是“,”,组件默认按逗号读就会出现一整列数据塞进第一个字段的情况。UTF-8 编码同样常见——老系统导出的 CSV 不带 BOM,按 ANSI 读中文必乱码。所以我在这段里强制 UTF8 和分号,遇到抖动时再根据实际情况放宽。
JSON 比 CSV 复杂的地方在嵌套字段:数组里是 object,字段名带点路径,或者要取多级子节点。碰到这种结构,配置字段映射是常规做法,按“根节点.子节点”的方式声明。组件不一定都能自动展开嵌套,但完全自己写 JSON 解析又不划算——先翻源码看 JSON reader 支持到什么程度,再决定手工补代码。
4.3 用字段映射吞掉“脏表头”:中文列名、空格和重复列名
Excel 表头经常不是合法 Delphi 标识符:带空格(“客户名称”还好,遇到“客户 名称”就有空格)、以数字开头、同一列名重复。TClientDataSet 的字段名不允许空格和歧义,导入引擎会做转换,但转换结果不一定是你想要的。
解决办法是用字段映射,按列序号把目标字段名和类型固定:
ADImport.FieldMap.Clear; ADImport.FieldMap.Add(0, 'CustCode', ftString); ADImport.FieldMap.Add(1, 'CustName', ftString); ADImport.FieldMap.Add(2, 'Amount', ftFloat); ADImport.FieldMap.Add(3, 'OrderDate', ftDate);这是很常用的能力:不依赖表头文字,只依赖列位置。当业务人员改了表头(比如新增“备注”列),只要列顺序不变,映射仍然可用。反过来,如果需求是“表头变化后也能自适应”,就用 HasHeader 自动生成字段名,再在事件里做类型覆盖。两条路线取决于导入文件是机器生成还是人工维护——人工维护的表,列位置映射几乎是最稳的。
字段映射同时解决了类型推断问题:列里混入脏数据时,ftString 字段能装下任何内容。凡是无法保证数据纯度的列,我优先把类型定为 ftString,落库时再做一次显式转换。这样导入不会因为一条“N/A”直接中断,用户交付体验好很多。
5. 避坑:EMS Advanced Data Import 在 Rad Studio 12 Athens 下的 5 个经典现场
这一章列的是实际压过的坑,按“现象 → 原因 → 解决”写。每个不一定只属于 EMS,但在这个控件上出现的频率最高。
5.1 现象一:编译期报内存错误,IDE 直接崩
现象:在 IDE 里编译带这个控件的工程,Delphi 提示内存错误,偶尔 bds.exe 直接消失。原因:IDE 是 32 位进程,工程引用了较大的 BPL 集合或 Source 目录里 .pas 太多,加上 IDE 的 Code Insight 占用,32 位地址空间被撑爆。解决:优先用命令行 msbuild 编译,把 Source 目录搜索范围收窄;必须用 IDE 时,关闭不相关的工程组,清理一次 IDE 缓存和工程目录下的临时文件再编译。这个现象不是控件本身 bug,而是 Delphi 12 环境在大工程下资源管理的老问题。
5.2 现象二:xlsx 能导,xls 报“文件格式无效”
现象:同一套代码,.xlsx 正常,老 .xls 文件打开就报格式无效。原因:组件对两种格式走不同读取器。xlsx 是 ZIP+XML 解析,xls 是 BIFF 二进制解析,有的发行版默认不包含老格式支持,需要另配单元。解决:先看 Source 里有没有独立 xls reader 单元;没有就统一要求客户提供 xlsx,或在导入前用 Office COM 把 xls 转存成 xlsx。这个坑的教训是:不要因为控件叫 Advanced Data Import 就默认全格式通吃,格式支持矩阵以包内 Docs 为准。
5.3 现象三:CSV 中文全是乱码
现象:CSV 里中文导入后显示成“锟斤拷”或问号。原因:编码不匹配。组件默认按 ANSI 或系统 Locale 读取,而现代 CSV 大多是 UTF-8,不带 BOM 的文件最容易被误判。解决:显式设置 Encoding 为 TEncoding.UTF8;如果还乱码,用记事本另存为带 BOM 的 UTF-8 再试一次。顺带建议:给客户发导入模板时固定用 xlsx,不要发 CSV,能省掉大量编码问题电话。
5.4 现象四:Excel 里“001”被导成了 1
现象:物料编码列以 0 开头,导入后前导 0 全部丢失,编码从“001”变成“1”。原因:类型推断把它识别成整数,整数不保留前导零。解决:给该列配字段映射为 ftString,或在导入前要求业务方把 Excel 列格式设为文本。这条特别适合用 4.3 节的字段映射处理,同时代码里观察编码列:只要可能带前导 0,一律按字符串导入。
5.5 现象五:表头有中文,生成的字段名不合法
现象:导入后字段名变成一串下划线或乱码,DBGrid 显示不出表头。原因:Excel 中文表头被转换成合法标识符,但转换规则不符合显示预期,“客户名称”可能变成“Field1”或带下划线的怪名字。解决:用字段映射固定目标列名,或导入后遍历 Fields,用 DisplayLabel 显示中文、FieldName 保持合法标识符。这个做法的额外好处是,后续 SQL 落库时字段名稳定,不会随源文件表头变化而漂移。
6. 进阶:把导入封装成后台线程里的 DataImportService,并处理大文件进度
导入功能通常在界面上触发,数据量一到几万行 UI 就会卡死。TADImport 解析期间阻塞调用线程,标准做法是放进后台线程。注意一个小坑:TADImport 实例不要跨线程共享,每个线程创建自己的实例,解析完把 CDS 回传主线程。
6.1 线程模型:一个后台线程一个 TADImport 实例
常见做法是在 TThread 里包一层,把第 4 章的 ImportFileToCDS 挪进线程执行。VCL 里我喜欢用 TThread.Queue 抛匿名方法,在主线程中绑定 DataSource。进度反馈用 OnProgress 事件或每解析 N 行发一条消息都可以,关键是把“解析”和“UI 刷新”解耦。线程内创建的 TClientDataSet 回传主线程后,由主线程接管生命周期,线程里不再触碰它,避免句柄跨线程混乱。
6.2 大文件分批导入:用 StartRow 控制读取行段
文件几十万行时,一次性导入出的 CDS 内存吃不消。分段处理是常规做法:先扫描总行数,再用 StartRow 配合读取行数每次导入一段,追加到最终结果集。分段的好处是每段解析完可以先清洗、落库,内存峰值可控。注意每段要重新设置 HasHeader:只有第一段读表头,后续段都设为 False,否则每段开头都会多出一条脏表头数据。
我早年做批量导入时偷懒没分段,用户扔进来一个 80MB 的 CSV,CDS 创建字段时就占了几百 MB 内存,直接在客户机器上卡死,后来才换成按行流式分片。所有带 Full Source 的导入控件都值得先读一遍它的行读取接口,再决定一次导入还是分批导入。这条血泪经验帮我省了后续大量售后,希望帮到你。
本文还有配套的精品资源,点击获取