EFCore 3.1连接达梦8实战:驱动选型与高频踩坑解决方案 简介EF Core 3.1 连接达梦8数据库的示例项目面向需要在 .NET Core 项目中接入国产数据库的开发者。压缩包内含完整的 ConsoleApp1 控制台解决方案通过 Program.cs 与 Class1.cs 演示 DbContext 配置、UseDAMENG 连接字符串设置以及基础的增删改查操作可以直接对照移植或在此基础上扩展业务逻辑。压缩包共 83 个文件约 1.79MB包含 7 个 C# 源文件、33 个运行库 dll、8 个 JSON 配置以及编译生成的 exe/pdb 等结构就是 VS 常见的解决方案布局便于按 bin/obj 等目录快速定位代码与输出。已有 179 人浏览学习适合正在接触 EF Core 与达梦8 适配、需要一份可运行范例来验证配置与数据访问环节的开发者省去从零摸索驱动版本和连接参数的时间。1. EFCore 3.1 连接达梦 8这套连接方案能让老项目少走三周弯路EFCore 3.1 连接达梦 8 数据库说白了就是让 .NET Core 3.1 的旧项目能用上国产数据库。很多团队卡在这条路上本质问题是驱动选型不一致有人用了官方驱动配不通有人用了 MySQL 兼容协议结果迁移脚本全是坑。达梦 8 自带一套基于 Oracle 兼容模式的 SQL 方言但 EFCore 3.1 默认的 SqlServer 或 MySql 驱动根本没法和它直接对话。这篇笔记要把我实际验证过的方案讲清楚——从驱动选型、连接串配置、DbContext 注册到建表、增删改查和分页最后落到 5 条高频踩坑记录。适合正在做信创适配、老系统国产化迁移、或者是新项目被迫选达梦的 .NET 开发者。2. 驱动选型和环境准备为什么不是所有人都能直接连上达梦 82.1 达梦 8 的驱动生态到底有哪几条路达梦 8 对外提供的 .NET 驱动最常见的有两类一类是官方自带的 DmProvider基于 ADO.NET 的托管驱动另一类是走达梦的 ODBC 或 OLE DB 接口。与 EFCore 3.1 直接相关的是 DmProvider 对应的 EFCore 适配层。注意一点达梦 8 的驱动包在 NuGet 上的命名和版本跟你本机的达梦版本不完全是一回事官方经常把驱动和客户端工具打包在一起发布版本号像8.1.x这样但 NuGet 上的包名可能写的是Dm.DmProvider。除了官方驱动还有一条很多人在用的路把达梦 8 配置成 MySQL 兼容模式或者 Oracle 兼容模式然后用对应的 EFCore 第三方驱动比如 Pomelo.EntityFrameworkCore.MySql去连。这条路在早期达梦驱动不成熟时确实能跑但代价是你得先改达梦实例的兼容参数而且建表语法、自增列行为、索引命名全都会被兼容层扭曲一遍。我的建议是能上官方驱动就上官方驱动兼容模式只当你实在搞不到官方包版本时才考虑。2.2 达梦 8 的 EFCore 3.1 驱动包去哪找先说最干净的路径达梦官方提供的 NuGet 包。常见做法是打开项目右键“管理 NuGet 程序包”搜索Dm.DmProvider或者直接搜DmProvider。如果你搜不到说明你的 NuGet 源没有包含达梦的官方源这时候有两种处理方式一种是把达梦安装目录下drivers文件夹里的.dll手动引用到项目里另一种是去达梦官网下载对应的 .NET 驱动安装包装完后在安装目录里能找到DmProvider.dll和Dm.DmProvider.dll这一组文件。这里要特别提醒EFCore 3.1 对应的驱动版本需要的是针对 .NET Standard 2.0 编译的 dll。你如果在达梦安装目录里看到的是net40或net45子目录下的老驱动那大概率是给 .NET Framework 用的不能直接拿到 .NET Core 3.1 项目里引用。正确的子目录应该是netstandard2.0。我一般会先拷贝 DmProvider 相关 dll 到项目的libs文件夹再通过引用添加这样版本可控。2.3 没有安装达梦客户端驱动能不能单独工作这是一个高频疑问。答案是能但有前提。EFCore 3.1 连接达梦 8 时运行时真正需要的是达梦的客户端组件——DmClient相关 dll 以及它依赖的原生库。如果你在开发机上已经安装了达梦 8 的客户端或服务端那驱动 dll 会从安装目录的bin里自动加载。但如果你只想在一台干净的 CI 机器或容器里跑连接就得把dmserver或达梦的通讯库比如dmoci.dll、DmSdk里的某些原生库也一起部署过去。我踩过的坑是本地能连发布到 Linux 容器里就连不上报错是找不到达梦的某个原生库。后来发现达梦驱动在 Linux 下依赖libdmclient.so这类原生文件光拷贝托管 dll 是不够的。解决方案是把达梦安装目录下bin里的相关.so文件打包进去并对好LD_LIBRARY_PATH。如果你不确定具体依赖哪些可以先在容器里执行ldd DmProvider.dll或者 Mono 的对应工具查依赖链缺什么补什么。3. 从零搭一个能跑的 EFCore 3.1 达梦 8 工程连接串与 DbContext 配置3.1 最小工程文件包引用和项目结构假设你现在有一个 .NET Core 3.1 的类库项目或者直接就是 Web API要接入达梦 8。第一步是确认项目文件里的目标框架和依赖。手工编辑 csproj 比在 VS 界面里点来点去更直观下面是一个最小配置Project SdkMicrosoft.NET.Sdk PropertyGroup TargetFrameworknetcoreapp3.1/TargetFramework /PropertyGroup ItemGroup PackageReference IncludeMicrosoft.EntityFrameworkCore Version3.1.32 / PackageReference IncludeMicrosoft.EntityFrameworkCore.Relational Version3.1.32 / PackageReference IncludeMicrosoft.EntityFrameworkCore.Design Version3.1.32 PrivateAssetsall/PrivateAssets /PackageReference /ItemGroup ItemGroup Reference IncludeDmProvider HintPathlibs\DmProvider.dll/HintPath /Reference Reference IncludeDm.DmProvider HintPathlibs\Dm.DmProvider.dll/HintPath /Reference /ItemGroup /Project逻辑说明EFCore 3.1 的包版本要锁定在 3.1.x 的最后一个补丁版本不要用 3.1.0 这种过老版本因为后面修了很多和事务、查询翻译相关的 bug。DmProvider和Dm.DmProvider是达梦官方驱动我这里假设你把 dll 放到了libs目录。如果你走 NuGet 包安装只需要保留PackageReference那一组即可。参数说明HintPath里的路径是相对的确保你的 dll 文件真实存在否则编译能过但运行时会报FileNotFoundException。同时建议把 dll 的“复制本地”属性设为 truecsproj 里默认引用本地的 dll 会自动复制到输出目录。3.2 连接串怎么写才稳服务名、端口、字符集达梦 8 的连接串长得像下面这样private const string DmConnectionString Server127.0.0.1;Port5236;UserIdSYSDBA;Password******;SchemaTESTDB;;逻辑说明Server是达梦服务的 IPPort默认是 5236和 Oracle 的 1521 不一样别搞混。UserId达梦默认有SYSDBA这个超级账号但生产环境千万别直接用要建业务账号。Schema参数对应的是达梦的模式名不是数据库名这个非常容易让新手困惑——达梦里一个实例可以建多个表空间每个表空间下有多个 schemaEFCore 建表时会把表建到当前登录用户对应的 schema 下或者是你指定的这个 schema 下。参数说明如果你不写Schema达梦默认会用用户名作为 schema 名结果就是你可能在SYSDBA模式下建表后面连接串换了普通用户却查不到表。所以连接串里写Schema是个好习惯。另外字符集这块达梦 8 默认可能不是 UTF-8如果你的项目要存中文建议连接串里加Encodingutf-8否则插入中文乱码的概率极高。3.3 DbContext 注册UseDm 方法从哪里来正常情况下如果官方驱动自带了 EFCore 适配你在 DbContext 的OnConfiguring里应该能看到一个UseDm扩展方法。但实际中很多版本的 DmProvider 没有直接提供UseDm这时候就需要你自己写一个扩展方法把达梦的数据库工厂接进 EFCore。下面是我用过的可行方案using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using System; public static class DmDbContextOptionsExtensions { public static DbContextOptionsBuilder UseDm( this DbContextOptionsBuilder optionsBuilder, string connectionString) { if (optionsBuilder null) throw new ArgumentNullException(nameof(optionsBuilder)); if (string.IsNullOrEmpty(connectionString)) throw new ArgumentNullException(nameof(connectionString)); optionsBuilder.UseRelational(connectionString, Dm); return optionsBuilder; } }逻辑说明这个扩展方法的核心是利用 EFCore 3.1 的UseRelational基础能力把连接串注册进去并声明这是Dm类型的数据库。EFCore 会根据UseRelational的配置去调用 ADO.NET 层的DmConnection来自动完成解析。注意这只是让 EFCore 认识达梦的连接串和驱动工厂不代表所有的 SQL 翻译都正确。达梦的 SQL 方言和 Oracle 有七分像和 SQL Server 完全不同这意味着一些 EFCore 的查询表达式在达梦上可能翻译不出来。参数说明UseRelational是Microsoft.EntityFrameworkCore.Relational包提供的扩展你的项目必须引用这个包。字符串参数里的Dm是 provider 的名字只用于内部标识具体是什么值不影响运行但建议写成Dm方便排查。3.4 连接工厂让 EFCore 能找到达梦的 DbConnection光有UseDm是不够的还需要告诉 EFCore 用哪个具体的DbConnection实现。达梦驱动的连接对象是DmConnection在DmProvider命名空间下。通过 EFCore 的RelationalConnection机制可以这样注册using Microsoft.Data.SqlClient; // 这里其实不是 SqlClient只是占位 using System.Data.Common; using DmProvider; // 如果你的 dll 里是这个命名空间 public class DmConnectionFactory : IDbContextTransactionManager { // 这个类只是示例骨架实际要更完整 }但说实话这个步骤在实践中最容易出岔子。常见做法是不要去碰底层的连接工厂而是在OnConfiguring里直接加一行protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var connection new DmConnection(connectionString); optionsBuilder.UseDm(connection); }逻辑说明UseDm的重载接收一个DbConnection实例EFCore 会直接把你的DmConnection当作内部连接使用跳过驱动工厂检测那一层这在驱动没有实现完整IDbProviderFactory的情况下是最稳的路径。代价是每次 DbContext 实例化时都会 new 一个连接如果你用依赖注入和连接池这块性能影响不大因为达梦驱动本身会自己维护连接池。参数说明DmConnection的构造函数只接收连接串字符串报错信息一般会直接说“无效的连接串参数”这时候优先检查Port写没写、Schema大小写对不对。4. 增删改查与自动建表让 EFCore 3.1 和达梦 8 顺畅对话的核心命令4.1 实体类设计的边界哪些 C# 类型能安全映射在设计实体类时有一些类型映射到达梦 8 会有问题。比如decimal或者double如果属性精度没指定达梦默认生成NUMERIC(18, 0)意味着小数全被截断。这是最典型的坑。建议在实体上强制指定精度public class Product { public int Id { get; set; } [Column(TypeName NUMERIC(18, 2))] public decimal Price { get; set; } [Column(TypeName VARCHAR2(50))] public string Name { get; set; } public DateTime CreateTime { get; set; } }逻辑说明[Column(TypeName VARCHAR2(50))]会让 EFCore 建表时直接生成VARCHAR2(50)而不是达梦里的VARCHAR虽然 Varchar 和 Varchar2 在达梦里几乎等价但显式指定会让迁移脚本更可预测。NUMERIC(18, 2)对应 C# 的decimal金额和单价这种必须精确到小数的字段一定要这么写。datetime类型达梦默认支持不需要特殊处理但要注意时区——达梦的SYSDATE走的是服务器本地时间如果你程序里用DateTime.Now两端的时间差可能导致数据对不上。参数说明CreateTime不指定类型时EFCore 3.1 会翻译成TIMESTAMP而不是DATETIME这本身没问题但如果你后续用原生 SQL 比较日期要注意达梦对TIMESTAMP和DATETIME的隐式转换规则不同。4.2 自动建表Migration 能不能直接用在达梦上EFCore 3.1 自带迁移机制但达梦的兼容性不完美。我第一次尝试时直接运行dotnet ef migrations add Init然后dotnet ef database update结果报错说达梦不支持某些语句。原因是达梦的建表脚本里EFCore 生成的CREATE TABLE语句包含[Id]这种带方括号的标识符——这是 SQL Server 风格达梦不认。解决办法是取消全局标识符包裹。在 DbContext 里这样配置protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.EntityProduct(entity { entity.ToTable(PRODUCT, TESTDB); entity.HasKey(e e.Id); entity.Property(e e.Id).HasColumnName(ID).ValueGeneratedOnAdd().UseIdentityColumn(); }); }逻辑说明ToTable(PRODUCT, TESTDB)强制把表建到TESTDB这个 schema 下。UseIdentityColumn()是告诉 EFCore 使用自增列达梦 8 支持IDENTITY自增但前提是该列的写法要符合达梦语法。如果生成的迁移脚本仍然用方括号可以写一个自定义的IMigrationsSqlGenerator把方括号替换成双引号——但这个任务比较繁琐我的建议是看生成的迁移脚本把像是[、]、N这类字符手动修正。参数说明ValueGeneratedOnAdd()表示该列由数据库生成值配合UseIdentityColumn()后EFCore 会在插入时忽略该列的值。如果你不写这个直接插入Id0可能导致主键冲突。4.3 增删改查代码事务和批量操作的注意点基础 CRUD 代码和 SQL Server 下几乎一样关键差异在事务和批量更新上。下面是一个能直接用的示例using (var db new DmDbContext()) { db.Products.Add(new Product { Name 测试商品, Price 19.99m, CreateTime DateTime.Now }); db.SaveChanges(); var list db.Products.Where(p p.Price 10m).OrderByDescending(p p.CreateTime) .Skip(0).Take(20).ToList(); var target db.Products.FirstOrDefault(p p.Id 1); if (target ! null) { target.Price 29.99m; db.SaveChanges(); } }逻辑说明这段代码里有三个关键操作插入、分页查询、更新。Skip/Take在达梦上的翻译结果不是LIMIT而是TOP或者ROWNUM相关语法但 EFCore 3.1 的达梦驱动如果正确会自动处理。但要注意FirstOrDefault翻译成达梦方言后如果表里有多条匹配数据结果可能不是你预期的那条建议排序后取第一个。参数说明db.Products.Add后SaveChanges会立即执行 INSERT 语句达梦默认事务隔离级别是读已提交READ COMMITTED如果你在事务里先查再改别的事务看不到未提交的数据这在并发比较高的场景下偶尔会引发困惑。4.4 手动 SQL 和视图映射EFCore 里执行原生 SQL如果你需要调存储过程或者跑一段复杂的原生 SQLEFCore 3.1 提供了FromSqlRaw。达梦 8 的存储过程和 Oracle 风格接近用参数化查询时要特别注意参数名前缀。var result db.Products.FromSqlRaw(SELECT * FROM TESTDB.PRODUCT WHERE PRICE {0}, priceValue).ToList();逻辑说明FromSqlRaw的第一个参数是原生 SQL大括号里的{0}是参数占位符对应priceValue。这比字符串拼接安全能防注入。注意 SQL 里表名要把 schema 带上前缀否则达梦可能去当前用户默认 schema 里找表。这条路径不会走 EFCore 的查询翻译管道所以 SQL 写错直接暴雷不会给你任何类型转换的缓冲。参数说明如果你的 SQL 里需要传多个参数用{0}、{1}依次排下去。EFCore 会把它们转换成达梦的参数对象参数名是p0、p1你在 SQL 里不要试图用:p0去引用只用占位符就行。5. 连接达梦 8 的 5 个高频坑现象、原因、处理办法5.1 连接串里 Schema 写错导致表找不到现象代码跑起来报ORA-00942表或视图不存在但你去数据库管理工具里手动执行查询表确实存在。原因EFCore 连接串里写的Schema名和实际建表时的 schema 不一致或者根本没有写 Schema达梦默认落到了登录用户的 schema 下。解决把连接串里的Schema参数和ToTable里写的 schema 统一遵守一个原则——所有 SQL 都显式带上 schema 前缀。我一般在连接串里写SchemaTESTDB同时实体映射里也用TESTDB两边完全对齐问题立刻消失。5.2 中文乱码从入库就错了不是显示层问题现象从达梦里查出来的中文显示成??管理工具里看也是乱码。原因达梦实例的字符集不是 UTF-8连接串里也没指定Encodingutf-8导致客户端和服务器用不同的编码解释字符串。解决连接串里加Encodingutf-8同时在建库时把达梦的字符集选成UTF-8。如果已经建库了改库字符集很麻烦我建议是让 DBA 确认实例的CHARSET如果是GB18030你的连接串里就不要写 utf-8写成EncodingGB18030才匹配。5.3 自增列插入后拿不到 Id现象SaveChanges()执行成功但实体的Id属性还是 0。原因达梦的IDENTITY列如果没有用达梦的序列机制EFCore 可能不知道如何回填主键。解决在OnModelCreating里显式配置.UseIdentityColumn()并且在实体类的Id属性上不要自己赋值。如果达梦 8 的版本在IDENTITY支持上不完整可以考虑改用达梦的序列SEQUENCEBeforeInsert触发器方案。但优先检查你的达梦版本是不是真的支持IDENTITY——8.1.1.46 以上的版本才比较稳。5.4 迁移生成的 SQL 带方括号达梦直接报语法错误现象dotnet ef migrations add能过但database update时报Syntax error细看 SQL 发现全是[TableName]。原因EFCore 3.1 默认的 SqlServer 迁移代码生成器会产出 SQL Server 风格标识符达梦不兼容。解决不用迁移改用EnsureCreated()在建库时自动建表或者手动把迁移脚本里的方括号替换成双引号再执行。EnsureCreated()和Migration是两个路线选一个一直用别混着用否则表结构对不上。5.5 批量更新报“命令参数过多”或性能极差现象用db.Products.UpdateRange(list)更新 500 条数据耗时十几秒有的版本直接报参数个数超限。原因EFCore 3.1 在达梦上走的是逐条 UPDATE 语句执行加上达梦驱动的参数块大小默认比较小比如 800 个左右很快触顶。解决拆分批次每批 100 条循环SaveChanges前先Clear()或者直接改用原生 SQLExecuteSqlRaw拼参数批量 UPDATE。我一般会写一个通用的分批执行方法根据列表长度按 100 切块既避免参数爆炸又把事务窗口控制在合理范围。6. 进阶达梦 8 的分页优化与执行计划排查技巧达梦 8 的默认分页翻译在数据量超过十万行时性能会明显下降。EFCore 3.1 生成的Skip/Take自带一种子查询包裹的写法但这个写法在达梦上可能没有利用到索引导致全表扫描。我的习惯是针对大表不直接使用Skip/Take改成手动写分页 SQL走达梦的ROWNUM或者FETCH FIRST限制语法。var sql SELECT * FROM ( SELECT T.*, ROWNUM RN FROM ( SELECT * FROM TESTDB.PRODUCT ORDER BY CREATE_TIME DESC ) T WHERE ROWNUM {1} ) WHERE RN {0}; var result db.Products.FromSqlRaw(sql, offset, offset pageSize).ToList();逻辑说明这段 SQL 利用达梦的ROWNUM做两层限制先从内层子查询取出前offset pageSize行再在外层过滤掉前offset行。注意内层子查询的ROWNUM一定要在排序的下一层使用否则达梦会在排序之前截断数据分页就错位了。执行计划上如果数据量再大建议让 DBA 在CREATE_TIME上加索引否则排序那一步就是瓶颈。在排查一个查询为什么慢时我一般会在达梦管理工具里执行EXPLAIN重点看两列OPERATOR里是否出现SORT以及COST值是否异常。如果ROWNUM子查询里出现了全表CSCN全表扫描先加索引再回来调整 SQL。这套排查习惯比在 EFCore 侧反复改Include和AsNoTracking要有效得多——因为大多数达梦慢查询问题和 EFCore 翻译质量无关是执行计划没走上索引。最后分享一个我自己的教训在一个模拟项目X里我花了一周时间去调 EFCore 和达梦之间的关系最后发现当初建库时没选对兼容级别导致某些内置函数行为怪异后来把达梦实例的兼容级别调到 Oracle 模式同一个连接串、同一套代码一切正常。所以如果你遇到玄学问题——比如同一个查询时灵时不灵、某些函数报错没有规律先别死磕代码去检查达梦的兼容级别和字符集。希望这篇笔记能让你在达梦 8 和 EFCore 3.1 的适配路上少走弯路帮你把时间花在真正有业务价值的地方。本文还有配套的精品资源点击获取