简介:这是一份 unidac 8.3.1 的完整源码版资源包,专供 Delphi 和 C++Builder 开发者使用。unidac 本身是一套成熟的多数据库访问引擎,原生支持 Oracle、SQL Server、MySQL、InterBase、Firebird 等主流数据库;取得源码后可以在不同 IDE 版本下自行编译安装,作者亲测从 Delphi 7 到 Delphi 10.4.1 均可用,适合需要跨数据库统一开发、控件二次定制或深入阅读底层机制的程序员。整包约 27.44MB,共 2000 个文件,其中 dpk、dproj、res、cfg、bdsproj 等工程配置类文件便于快速定位不同编译环境的工程入口,pas 源码文件承载控件核心实现,dfm、fmx 文件保存窗体界面资源,inc、dpr、bat 文件分别提供编译宏、主程序与批量构建脚本,目录结构清晰,便于按需检索。目前已有 651 人学习下载。借助这份源码,既可以解决 d7~xe10.4.1 各版本下的控件安装问题,也能对照工程配置和源码模块理解 unidac 的数据库连接管理、SQL 生成及事务处理机制,是 Delphi 数据库开发中一份完整的参考素材。
1. 为什么一个数据库连接组件值得你花时间看源码
如果你手里同时管着几套老系统,一套用 SQL Server,一套跑 PostgreSQL,还有一套嵌着 SQLite,你会发现程序员最常干的事不是写业务,而是在各种数据库驱动之间反复横跳。改一个分页写法要查驱动文档,换一个部署环境要重编依赖,调试一个连接池泄漏问题得开着三个官方的示例工程对比。unidac_8.3.1_src 源码版解决的就是这个问题——它把常见数据库的访问统一成一套组件模型,让你用同一套代码面对不同的数据库后端。
这套源码版的价值不只是能编译出一个控件安装包,而是把连接、事务、数据集、SQL 解析这一整条链路的实现都摊开在你面前。你可以看到它对不同数据库差异的处理逻辑,可以去掉你用不到的驱动来缩小分发体积,甚至可以在出问题时直接定位到是哪一层在报错。它适合两类人:一类是要在项目里集成多数据库支持、想省掉重复适配工作的开发者,另一类是遇到官方二进制版解决不了的怪问题、需要扒开底层看个究竟的排错型选手。这篇文章会把它的结构、安装、配置和常见坑一次讲透。
2. 源码版的项目结构:先搞清楚你拿到的是什么
2.1 源码目录与核心单元文件的对应关系
解压 unidac_8.3.1_src 之后,第一件事不是急着装控件,而是先把目录结构认清楚。整个源码包的核心逻辑集中在 Source 目录下,其中 Uni.pas 是统一入口单元,它声明了 TUniConnection、TUniQuery、TUniScript 这些你在设计期拖到窗体上的类。真正的数据库差异处理被拆到了各个独立的驱动单元里,比如 SQL Server 对应的是 UniProvidersSQLServer.pas,PostgreSQL 对应 UniProvidersPostgreSQL.pas,SQLite 则落在 UniProvidersSQLite.pas。
每个驱动单元里都有几个关键的类,比如 TSQLServerProvider、TPgProvider、TSQLiteProvider,它们负责把 Uni 层发的抽象命令翻译成各数据库自己的协议。这种架构的好处是,你在代码里写的 TUniQuery 并不直接面对某个数据库的 API,而是通过 Provider 层做了一个适配。所以你新增一种数据库支持时,不需要动上层代码,只要照着现有 Provider 的模式补一个单元文件即可。
在编译安装之前,建议把 Source 目录里的 .pas 文件都过一遍单元名,尤其是 Uni.pas、UniProvider.pas 和 DBAccess.pas 这三个文件,它们是一个层级关系。Uni.pas 面向你写业务代码时用到的组件,UniProvider.pas 定义 Provider 的基类和接口契约,而 DBAccess.pas 是更底层的数据库访问框架,负责连接生命周期、命令执行、结果集缓存这些通用机制。
2.2 源码包里各版本分支的选择:Delphi 版本和平台决定了你编译哪个包
源码包里通常会按 IDE 版本放好对应的包工程文件,常见的命名方式是 d10、d11、d12 这种代号,对应不同的 Delphi 版本。你在打开包之前,先确认自己用的 IDE 是哪个版本,然后只打开对应的 .dpk 文件。比如你在 Delphi 10.4 上工作,那就找名字带 d10 的包,不要试图用新版本的 IDE 直接打开旧包然后指望它自动升级,那样十有八九会报一堆找不到单元的错误。
包文件的组织还分设计期包和运行期包。设计期包的作用是让组件出现在 IDE 的组件面板上,方便你拖拽,而运行期包是程序真正链接时用的。你至少要编译并安装设计期包,否则组件面板上看不到 TUniConnection。如果你不希望最终分发程序依赖运行时包,可以在工程选项里把运行时包设为静态链接,这样发布时只需要带上编译出来的 DCU 或最终 exe,省去了一堆 bpl 文件的麻烦。
还有一个容易忽略的目录是 Units 或者 Output,编译产生的 DCU 文件默认会输出到那里。如果后面你打开了别的工程却提示找不到 Uni 相关单元,多半是 IDE 的 Library 路径没有包含这个输出目录。把输出目录加到 Library 搜索路径里是一个很值得养成的习惯,省得每次新建工程都要重新配置。
2.3 源码版和安装版在组件行为上的差异:自己编译时容易忽略的编译选项
源码版和官方安装包版在运行表现上可能存在差异,根源往往不在代码本身,而在于编译选项。官方二进制版默认开启了某些功能开关,而你自己编译时如果没有定义对应的编译指令,那些功能就会被静默去掉。比如有些版本里对特定数据库的 SSL 支持是靠条件编译指令控制的,你在源码里能看到类似 {$IFDEF UNIDAC_HAS_SSL} 这样的分支,如果编译时没有定义这个符号,相关代码就不会被编进去。
另一个典型差异是字符集的处理。Uni 组件默认会按 AnsiString 来处理很多字符串操作,如果你的工程启用了 Unicode 编译,而源码包的某些辅助单元没有跟着用 Unicode 版本重新编译一遍,就会出现字符串截断、乱码这类问题。所以在编译源码版之前,建议先打开项目选项,核对一下 Unicode 相关的开关是否和你的主工程保持一致。
我在第一次编译这套源码时,遇到过明明报错说找不到某个单元,但 Sources 目录里文件明明就摆在那。后来发现是 IDE 的 Library 路径里同时存在两个不同版本的 Uni 目录,编译器优先搜索到了旧版本,导致新旧单元混用。这种问题一旦出现,会带来大量莫名其妙的编译错误,排查起来非常浪费时间。
3. 编译安装全过程:从源码到组件面板上的 TUniConnection
3.1 用命令行或 IDE 编译运行期包:以 Delphi 为例的最小操作步骤
打开 Delphi 后,先通过 Project Manager 打开源码包里对应你 IDE 版本的运行期包文件。如果你不确定自己的版本应该用哪个包,可以看包文件名末尾的数字,那通常就是 Delphi 版本的代号。打开之后,直接在 Project Manager 里右键点击这个包,选择 Compile。这一步会生成 DCU 文件,它们是之后设计期包编译所依赖的基础。
接着编译设计期包。设计期包的文件名里通常会带 Design 字样,它依赖刚才编译出来的运行期包。在 Project Manager 里右键点击设计期包,选择 Install,Delphi 会自动先编译再安装到 IDE 中。安装成功后,组件面板上会出现 UniDAC 相关的组件页签,里面排列着 TUniConnection、TUniQuery、TUniScript 等组件。
如果编译过程中弹出错误提示某个单元找不到,先去 Event 日志里看路径搜索记录。常见原因是当前包工程文件用相对路径引用了 Source 目录,而你的解压路径和包文件所在路径不一致。查看 Project Options 里的 Delphi Compiler 搜索路径,把 Source 目录的绝对路径加上去再重新编译。
编译完成后做一个快速验证:新建一个 VCL 工程,在窗体上放一个 TUniConnection 和一个 TUniQuery,检查 Object Inspector 里能否看到它们的属性列表。能正常显示属性,说明设计期控件注册和运行期单元都已经被 IDE 正确识别了。如果属性列表是空的,说明设计期包虽然装上了,但运行期包没有正确链接。
3.2 安装后 IDE 配置:组件面板的位置和 Library 路径的持久化
组件安装成功之后,下一步要确认 Library 路径是持久的。Delphi 在每次启动时会读取注册表或配置文件里的全局 Library 路径,如果你没有把源码目录或者编译输出目录加进去,只是当前工程临时搜索到了,那么下次新建工程时又会打回原形。打开 Tools -> Options -> Language -> Delphi -> Library,把 Source 目录加进去,特别是 UniDAC 的 Source 根目录和编译输出目录。
还有一点容易被忽略:如果同一台机器上装了多个 Delphi 版本,每个版本的 Library 路径是独立的。你不能指望 10.4 版本配好的路径在 11 版本里自动生效。所以每换一个 IDE 版本,都要重新确认一遍路径配置。
路径配置好后,再检查一下组件面板的排布。UniDAC 默认会创建一个独立的组件页签,里面包含所有核心组件。如果你之前装过别的数据库访问组件,页签里的组件可能会有重名冲突。遇到这种情况,建议在面板上右键,选择一个不冲突的页签名,把 UniDAC 组件单独放一个组。
调试这一步可以用一个最小工程来验证:放一个 TUniConnection,设置 ConnectDialog 属性,运行时弹窗输入 SQLite 数据库路径,点击连接。能正常连接成功,说明安装链路完整。如果连接失败,先检查是否缺少对应数据库的客户端运行库,因为有很多数据库驱动是依赖官方客户端库的,比如 Oracle 要 OCI 库、SQL Server 要看网络库配置。
3.3 源码里需要按需修改的编译选项:有条件编译指令怎么设
UniDAC 源码里散落着不少条件编译指令,它们控制着一些可选功能的开关。比如有的版本用 {$IFDEF UNIDAC_USE_ZXLIB} 来决定是否启用压缩支持来配合某些网络传输场景,有的用 {$IFDEF MSWINDOWS} 来区分 Windows 和 POSIX 平台代码。在编译之前,用编辑器的全局搜索功能搜一遍 {$IFDEF,你会看到所有可以被条件编译包裹的代码段。
这里我建议不要一开始就试图开启所有功能。条件编译开得越多,编译链路上的依赖就越复杂,你可能需要额外引入第三方库才能编过。对于绝大多数项目来说,默认的编译设置已经够用,按需开启具体的功能开关更稳妥。比如你明确知道目标环境需要 SSL 加密连接,才去搜索 SSL 相关的指令并打开它。
修改条件编译指令的方式无非两种:一是直接在源码里加 {$DEFINE 指令名},二是在 IDE 的项目选项里填写条件定义。我更推荐用第二种方式,因为它不会修改原始源码文件,后续升级源码包时可以避免合并冲突。填写条件定义时,注意多个指令用分号分隔,不要带空格。
4. 常用功能配置与参数:连接、事务、批量操作的实战设置
4.1 TUniConnection 的关键属性:Server、Database、ProviderName 的配置逻辑
TUniConnection 是 UniDAC 体系里的核心,几乎所有操作都是从它开始的。它最重要的属性是 ProviderName,这个属性决定了你底层连的是什么数据库。比如你连 SQLite,就设 ProviderName 为 SQLite;连 SQL Server,就设为 SQLServer。ProviderName 一旦确定,很多属性列表和默认值会自动跟着变化,比如 ConnectString 的语法提示就会按对应数据库的格式来。
连接字符串的书写方式在不同 Provider 之间有一些共同点,但也各有差异。以 SQLite 为例,最常见的形式是:
Database=D:\data\mydb.db;LockingMode=Normal这段连接串中,Database 指定数据库文件路径,LockingMode 控制的是并发读写时对文件锁的处理方式。SQLite 在多线程读写时容易出现 database is locked 的错误,改成 Normal 或 Exclusive 会直接影响并发表现。注意,SQLite 的 Provider 默认是直接调用本地库,不需要单独的客户端配置,这是它适合快速验证的原因。
对于 SQL Server,一个典型的连接串长这样:
Server=192.168.1.100;Database=appdb;User ID=sa;Password=xxxx;Pooling=True这里 Server 是数据库实例的地址,Pooling 控制连接池开关。值得留意的是,Uni 组件里的连接池机制是自有的,不是完全依赖数据库客户端的池化能力。当你把 Pooling 设为 True,UniDAC 会维护一个连接缓存,避免频繁创建物理连接带来的开销。
在写业务代码时,我一般会把连接配置集中放到 ini 文件或注册表里,不让连接串硬编码在代码中。这样换环境时只需要改配置文件,不需要重新编译。还有个小技巧:TUniConnection 的 ConnectDialog 属性可以挂一个连接编辑对话框组件,运行期按 Ctrl+G 就能弹出可视化配置界面,这在调试阶段查连接问题时非常好用。
4.2 TUniQuery 与 TUniScript:SQL 执行效率的差别和适用场景
TUniQuery 在用法上和普通的数据集组件差别不大,你可以在设计期设置 SQL 属性,运行时调用 Open 来取结果集,或者调用 ExecSQL 来执行不返回结果集的语句。它支持参数化查询,这是防止 SQL 注入和提升执行计划复用率的关键手段。
UniQuery1.SQL.Text := 'SELECT * FROM orders WHERE customer_id = :custId AND status = :st'; UniQuery1.ParamByName('custId').AsInteger := 1001; UniQuery1.ParamByName('st').AsString := 'PAID'; UniQuery1.Open;这段代码里,:custId 和 :st 是命名参数,ParamByName 用于给参数赋值。参数化查询的最大好处是数据库可以缓存执行计划,而不是每次都重新解析 SQL。尤其是同一个查询被反复执行且只有参数值不同的场景,性能差距会很直观。
TUniScript 则是用来批量执行一段多语句脚本的。它适合数据库迁移、初始化建表这类场景,你可以把一整段建表和插入语句放到 UniScript 里一次执行,而不需要手动切成一条条 SQL。它还支持事务控制,你可以设置一个脚本内的所有语句要么全部成功,要么全部回滚。
UniScript1.SQL.Text := 'CREATE TABLE temp_log (id INTEGER, msg TEXT);' + sLineBreak + 'INSERT INTO temp_log VALUES (1, ''start'');' + sLineBreak + 'INSERT INTO temp_log VALUES (2, ''end'');'; UniScript1.Execute;这个示例把建表和插入合在了一个脚本里。注意脚本内的 SQL 必须用目标数据库支持的语法,UniScript 并不会帮你做语法转译。它只负责按分隔符拆分并逐条下发执行,所以脚本的书写规范还是得你严格把关。
4.3 事务控制与批量更新:把多次往返做成一次提交
事务控制是数据库应用里绕不开的一环。TUniConnection 提供了 StartTransaction、Commit、Rollback 三个方法,它们对应数据库经典的事务三阶段。还有 Transaction 属性可以让你把一个独立的事务对象指派给多个数据集,这样多个查询可以共享同一个事务上下文。
UniConnection1.StartTransaction; try UniQuery1.ExecSQL; UniQuery2.ExecSQL; UniConnection1.Commit; except UniConnection1.Rollback; raise; end;这段代码里,两个 ExecSQL 被包在了一个事务中,任何一步抛异常都会触发 Rollback,保证数据不会出现半更新的状态。try/except 的异常处理是事务控制里最容易疏忽的一环,很多人只写了 Commit 忘了 Rollback,数据就脏了。
批量更新用于处理大量记录的写入。TUniQuery 支持 Array DML 模式,可以在一次网络往返里提交多条记录。打开方式是通过 Options 属性启用某些批量开关,或者直接使用 TUniLoader 组件。TUniLoader 是为大数据量导入设计的,它能以流式方式把数据推给数据库,不用像逐条插入那样频繁建连接。
UniLoader1.Connection := UniConnection1; UniLoader1.TableName := 'big_table'; UniLoader1.LoadFromDataSet(DataSet1);上面代码里,TableName 指定目标表,LoadFromDataSet 从一个已有数据集里取数据批量装载。相比在循环里逐条执行 Insert,这种方式能明显减少执行时间,在生产环境导初始数据时特别管用。需要留意的是,目标表和数据集的结构最好一致,字段名和类型不匹配时会直接报错。
5. 避坑手册:源码编译与使用中的高频问题排查
5.1 编译期报错找不到某一个单元但源码明明存在
现象:编译 UniDAC 的包时,IDE 提示无法找到某个 .pas 文件对应的单元,但你打开 Source 目录确认文件就放在那里。
原因:最常见的是 IDE 的 Library 搜索路径没有包含该文件所在目录,或者同时存在两个不同版本的 UniDAC 目录,编译器优先找到了旧目录里的同名文件。
解决:打开 Tool -> Options -> Language -> Delphi -> Library,清掉旧的 UniDAC 路径,把当前这份源码的 Source 目录放到最前。如果还不行,把当前工程的搜索路径也加上一份,并检查系统环境变量里是否残留着旧的路径设置。
5.2 组件安装成功但运行时报找不到 UniDAC 的 DCU 或 BPL
现象:设计期组件能拖到窗体上,编译也能通过,但运行程序时提示找不到某个 bpl 或者 DCU 文件。
原因:运行时包没有被正确安装,或者你在主工程中引用了运行时包但程序分发时没把 bpl 带全。另一种情况是 IDE 虽然知道路径,但最终 exe 运行时是靠系统路径找 dll 和 bpl 的,本机搜得到不代表目标机器搜得到。
解决:开发调试阶段,把编译输出的 bpl 所在目录加入系统 PATH;发布程序时,如果不想用户安装一堆运行时库,就把 Project Options 里的运行动态库链接改为静态链接,让 UniDAC 的代码直接编进 exe。
5.3 SQLite 连接时提示 database is locked
现象:程序里多线程或者多进程同时读写 SQLite 数据库,运行一段时间后稳定复现 database is locked 错误。
原因:SQLite 的锁粒度是整个数据库文件,写操作会独占文件锁。UniDAC 的默认锁定模式是 Normal,遇到高并发写就扛不住。
解决:把连接串里的 LockingMode 改成 Exclusive,并且打开事务批量写入,减少写操作次数。如果你没法改连接串,就参考 UniDAC 自带的一个额外的 SQLite 扩展库来启用 WAL 模式,WAL 模式能显著改善读写并发,但需要配套处理检查点机制。
5.4 连接 SQL Server 时提示协议错误或登录失败
现象:同一台机器上 ODBC 连 SQL Server 正常,但 UniDAC 连接时报协议相关的错误。
原因:UniDAC 的 SQL Server Provider 直接使用 TDS 协议,不走 ODBC。如果 SQL Server 实例配置禁用了对应协议,或者你在连接串里写的协议参数不对,就会失败。
解决:检查 SQL Server 的实例配置,确保 TCP/IP 协议已启用,且端口没有冲突。连接串里可以尝试去掉 Pooling 或者其他高级参数,先跑通最小连接串,再逐步加参数定位是哪个设置引发的冲突。
5.5 多版本源码混用导致的字符集乱码
现象:程序里查出来的中文字符显示为乱码,或者写入数据库后读出来是问号。
原因:源码包编译时的字符集设置和主工程不一致,或者主工程用了 Unicode 而 UniDAC 某些辅助单元是用 ANSI 编的。
解决:统一所有工程的字符集设置。建议主工程和 UniDAC 包都用默认 Unicode 设置,不要在某一个包上单独改字符集。检查代码里对字符串的处理,不要混用 AnsiString 和 string 类型去传参。
5.6 使用源码版发布程序后体积变大
现象:同一个功能,用官方安装版编译出来的 exe 是 10MB,用源码版编译出来变成 20MB。
原因:源码版默认把所有 Provider 都编了进去,哪怕你只连 SQL Server,其他数据库的驱动代码也进了 exe。
解决:在源码里裁剪掉不需要的 Provider 单元。比如只保留 SQLite,就从包文件的 Uses 列表里把其他 Provider 相关单元删掉,重新编译运行期包。这样做不仅减小体积,还减少了部分条件编译带来的外部依赖。
6. 更进一步的用法:把源码版编译成自定义驱动,并绕开官方不支持的限制
6.1 给已有 Provider 打补丁:修改源码后重编译的流程
很多场景下你会遇到官方二进制版表现正常但缺少你想加的功能的情况。既然手里是源码版,就可以直接在 Provider 层动手。比如你想在 SQLite Provider 里默认启用 WAL 模式,做法是打开 UniProvidersSQLite.pas,找到连接初始化的位置,在执行 PRAGMA 的代码段加入一行:
FConnection.ExecSQL('PRAGMA journal_mode=WAL');改完之后,编译运行期包,再把设计期包装一遍,然后调用一个新工程验证行为变化。这里有个必须养成的习惯:修改前把原始文件备份一份,并且把改动写到注释里标清楚原因和时间。否则下次源码包更新时,你很难判断哪些修改是本地的,哪些要重新合入。
6.2 增加一种私有数据库的支持:仿照现有 Provider 写一个最小实现
如果你内部有一套自己封装的数据库协议,或者要连的数据库 UniDAC 官方不支持,源码版给了你扩展的入口。操作思路是复制一个现有 Provider 单元,比如以 SQLite 为基础,然后修改 Provider 名称类和协议处理细节。这个工作量不取决于 UniDAC 这边,而取决于目标数据库的协议复杂度。
我建议先照着最小可用的目标来做:先实现连接建立、执行简单 SQL、取回结果集三个能力。一步一步补齐,不要一上来就追求支持全部数据类型和存储过程。在你的自定义 Provider 里,你需要重写 TUniProvider 的几个核心方法,特别是 GetConnection、GetCommand、GetDataSet 这些工厂方法,让返回对象指向你自己的实现类。
写完之后,把新单元加入包文件,编译安装。组件面板上会多出一个新的 Provider 名,你在 TUniConnection 的属性里就能看到它。这个思路同样适合用来制作一个内部定制的数据库访问中间层,你在企业里不需要把具体后端暴露给业务代码,只要暴露你的 Provider 名字就行。
6.3 验证自定义 Provider 是否可用的三组命令
验证自定义 Provider 是否可靠,从三个角度入手。第一是连接测试,用最简单的连接串在主工程里跑一段代码,确认能拿到连接对象且没有异常抛出。第二是元数据测试,读取表单列表、字段列表,确认数据库的结构信息能被正常枚举。第三是数据读写测试,反复执行插入、更新、查询,确认数据的读写路径没有断点。
with TUniConnection.Create(nil) do begin try ProviderName := 'MyCustom'; ConnectString := 'Server=test;Database=db1'; Connect; if Connected then ShowMessage('custom provider ok'); finally Free; end; end;这个示例在运行时动态创建一个连接,不依赖设计期组件,能更真实地反映 Provider 的可用性。注意 ProviderName 必须和你注册到设计期的 Provider 类名严格一致,大小写也不能马虎。
验证元数据的时候,我一般直接看 TUniMetaData 相关的调用结果。如果表列表能出来,说明底层协议里的元数据查询路径是通的;如果某些字段类型显示异常,那是类型映射没有配对,需要去 Provider 的类型转换代码里补分支。
6.4 遇到官方驱动不支持的数据类型怎么办:类型映射的调整思路
数据库返回的数据类型和 Delphi 原生类型不是一一对应的,UniDAC 通过字段类型映射来完成转换。如果自定义数据库里有一种特殊类型,比如一种二进制日志类型,默认映射成 ftMemo 可能会导致读取失败。这时需要找到 Provider 单元里的 DataTypeMap 相关处理代码,把目标数据库类型映射到你期望的 Delphi 字段类型上去。
DataTypeMap.AddDBTypeRule('MYLOG', ftBlob, 'UnicodeString');这段代码表达的意思是,数据库里的 MYLOG 类型,按 ftBlob 的方式处理,并映射为一个可变长度的字符串容器。实际参数名以你的源码版本为准,但思路是通用:先在映射表里找到入口,再按规则添加类型对应。
改完类型映射后,光重编译 Provider 还不行,最好跑一轮字段类型覆盖测试,把每一个数据库自定义类型对应的 Delphi 字段类型都列出来看一遍,确保没有映射遗漏。真正踩过坑的人都知道,类型映射出的问题要到数据量大了以后才暴露,经常是某些记录触发某个特殊值导致报错前期根本看不出来。
我常用的一个习惯是:在所有 Provider 改动完成后,我会用同一套业务代码分别连一次 SQLite 和 SQL Server,对比两边的行为一致性。因为源码版改动某处往往影响的是一个公共基类,不同数据库都会受到影响。对比测试能帮你快速抓到自己在公共代码里埋下的逻辑错误。希望帮到你。
本文还有配套的精品资源,点击获取