EFCore3.1连接达梦8实战:驱动、连接串与五个必踩的坑

发布时间:2026/10/9 23:28:18
EFCore3.1连接达梦8实战:驱动、连接串与五个必踩的坑
简介面向需要在.NET Core环境下接入国产达梦8数据库的开发者这份示例压缩包提供了基于EF Core 3.1的完整连接与操作工程。内含Visual Studio解决方案及ConsoleApp1控制台项目Program.cs与Class1.cs演示了继承DbContext、定义DbSet实体、配置UseDAMENG连接字符串等核心步骤同时覆盖实体新增、查询、修改、删除的典型写法并保留了NuGet依赖清单与项目配置项可帮助已有EF Core认知的读者快速理解达梦适配器的接入方式减少摸索官方文档的时间。资源共83个文件体积仅1.79MB其中33个dll程序集与4个exe可执行文件构成运行主体7个cs源文件提供关键示例代码8个json文件记录依赖与配置另含pdb调试符号及obj/bin构建缓存文件整体结构清晰适合在Visual Studio中直接打开sln还原并运行验证。目前已有179人学习下载示例短小而完整对初次将EF Core应用迁移至达梦8、需要参考真实项目结构的初中级开发者尤其实用也可作为后续集成更多业务实体时的起点模板。1. EFCore3.1连达梦8看着像换个连接串实际是一场小型国产化改造某公司一个跑在 .NET Core 3.1 上的老服务数据库要从 SQL Server 换到达梦8代码里全是 EF Core 3.1 的 DbContext升级框架版本基本不可能任务就变成了「EFCore3.1 连接达梦8」把连接串换掉祈祷能跑。结果第一小时就翻车——EF Core 的 Provider 决定了 SQL 生成、参数类型、自增列回填行为不是达梦兼容 SQL Server 语法就等于 EF Core 能直接驱动它。本文就从驱动、连接串、最小 Demo 到五个必踩的坑把这条改造路径完整铺一遍适合正在做信创适配、被达梦8困住的 .NET 开发者照着复现。2. 驱动与连接串换掉 UseSqlServer 之后还有三个参数要改2.1 先确认达梦8客户端里有没有 EF Core ProviderEF Core 想连接一个数据库前提是存在对应的关系型 Provider。SQL Server 有UseSqlServerPostgreSQL 有UseNpgsql达梦8则要看安装包里带了什么。达梦8 的客户端安装目录里常规会带一个 ADO.NET 驱动命名空间一般是Dm里面的核心程序集是 DmProvider部分版本会在同一目录里附带 EF Core 适配组件程序集名类似 Dm.EntityFrameworkCore里面提供UseDm扩展方法。打开安装目录搜EntityFrameworkCore和Dm开头的 DLL确认有没有这个东西是整个改造的第一步。这个检查决定了后续工作量。我的经验是能找到对应 EF Core 适配组件就选它改动量最小DbContext 和 LINQ 查询基本不动找不到就退到 ADO.NET 手写仓储把核心增删改查用 DmConnection 重写EF Core 只保留在少量不需要达梦特性的地方自己从零写一个 EF Core 关系型 Provider 是最不划算的需要实现连接、命令、迁移、值转换一整套内部接口普通业务项目根本养不起这个维护成本。方案改动量风险适用场景官方 EF Core 适配组件小依赖安装包是否附带有适配组件、DbContext 多、希望保留 LINQADO.NET 手写仓储中等可控需要重写数据访问层适配组件缺失、业务查询集中在少量核心表自研 EF Core Provider大维护成本高不建议几乎没有除非团队有框架开发能力顺便说一句不要因为达梦8兼容 SQL Server 的部分语法就直接用UseSqlServer去连达梦。EF Core 的 Provider 不只负责连接还负责生成 SQL、绑定参数、处理自增列回填。用 SQL Server 的 Provider 连达梦即使连接串能过执行 INSERT 时它会按 SQL Server 的方式生成OUTPUT INSERTED.ID达梦8对这个语法支持得并不统一最常见的结局是 SaveChanges 直接报 SQL 语法错误。这条血泪经验建议记在改造方案的第一页。2.2 使用官方适配组件的最小引用方式假设安装包里确实带了 EF Core 适配程序集引用到项目里之后写法上和 SQL Server 非常接近。ASP.NET Core 3.1 项目里在 StartUp 的 ConfigureServices 中注册 DbContextpublic void ConfigureServices(IServiceCollection services) { services.AddDbContextDemoContext(options { // UseDm 由达梦的 EF Core 适配组件提供具体扩展名以程序集为准 options.UseDm(Server127.0.0.1;Port5236;User IdSYSDBA;PasswordSYSDBA;SchemaDEMO); }); }UseDm是达梦适配组件对外暴露的扩展方法它做的事情和UseSqlServer一样就是把这个数据库的 Provider 相关服务注册进 EF Core 内部容器。注意写完之后如果编译报“DbContextOptionsBuilder 不包含 UseDm 的定义”别急着怀疑代码去检查程序集是否引用成功然后在引用的 DLL 里搜一下类文件看看真实扩展方法叫什么。不同小版本的达梦适配组件命名不完全一致有的叫 UseDm有的可能是 UseDm8这个以你手里那个 DLL 为准我把这种版本差异当成连接达梦8的默认设定。连接串里那行SchemaDEMO不是可有可无的。达梦8的 Schema 概念和 SQL Server 的 Schema 接近但行为更偏 Oracle用户登录后默认看到的是自己的默认 Schema如果连接串不指定 SchemaEF Core 生成的SELECT * FROM EMPLOYEE可能会到错误模式下找表结果就是一张明明存在的表报了“表或视图不存在”。我一般习惯在连接串里显式写 Schema并且全部用大写避免大小写转换带来的二次问题。2.3 连接串参数把超时、Schema、字符集一次设对达梦8 的 .NET 连接串参数在不同小版本上略有差异但下面这几个是高频出现的测试阶段建议逐个确认。参数作用建议值 / 说明Server数据库服务器地址127.0.0.1 或内网 IP不要带协议前缀Port达梦8服务端口默认 5236安装时改过就按实际端口来User Id登录用户一般测试用 SYSDBA生产环境用最小权限账号Password密码注意密码里如果有特殊字符该转义的转义Schema默认模式大写例如 DEMO不指定会造成找错表Pooling是否启用连接池测试阶段设 false定位问题更快稳定后再开 trueConnection Timeout连接超时建议 15~30 秒不要用默认的无限等待字符集也是一个容易在后期爆雷的点。如果库表字符集和连接串字符集不一致中文读写会出现乱码甚至部分字符导致 SQL 参数绑定时报错。测试开始时就把字符集统一好连接串上该加的参数加上省得后面排查数据问题时要分心怀疑编码。稳妥的排错顺序先用下面这段代码验证连接串本身是否可用再进 EF Core 层查映射问题。public static bool TryOpenDmConnection(string connStr, out string error) { try { // DmConnection 是达梦8提供的 ADO.NET 连接对象 using var conn new Dm.DmConnection(connStr); conn.Open(); conn.Close(); error null; return true; } catch (Exception ex) { error ex.Message; return false; } }这段代码的价值在于把问题分界连接失败就是网络、认证或连接串参数的事连接成功但 EF Core 查询报错才是 Provider 和映射的问题。我第一次接达梦的时候没做这个分层连接串参数错了两个小时没看出来后来拆出这个方法一分钟就定位了。建议把TryOpenDmConnection写进公共工具类改造期间所有环境排查都用它兜底。3. 最小可跑例子从 DbContext 到一条 INSERT 的完整链路3.1 建表和实体达梦8 的列类型选择演示场景用一张员工表建表 SQL 直接面向达梦8 执行。表名、列名全部用大写这是为了避免达梦8 的大小写规则在 EF Core 映射时产生“表或视图不存在”的副作用后面第五章会展开讲原因。CREATE TABLE DEMO.EMPLOYEE ( ID BIGINT IDENTITY(1,1) PRIMARY KEY, NAME VARCHAR(64) NOT NULL, BIRTHDAY TIMESTAMP, SALARY DECIMAL(18,2) );IDENTITY(1,1)是达梦8 自增列的写法对应 SQL Server 的IDENTITY(1,1)比 Oracle 风格的序列加触发器简洁得多。TIMESTAMP对应 .NET 的DateTime注意不要用DATE类型去映射DateTime达梦8 在部分兼容模式下DATE不带时分秒或行为不一致时间字段统一用TIMESTAMP能避免“日期丢了时间”的翻车。实体类保持简单属性名建议直接大写开头这样 EF Core 生成列名时和大写列名一致省去后续显式映射public class Employee { public long Id { get; set; } public string Name { get; set; } public DateTime Birthday { get; set; } public decimal Salary { get; set; } }在 DbContext 里配置表名和主键。ToTable(EMPLOYEE, DEMO)同时指定表名和 SchemaValueGeneratedOnAdd()告诉 EF Core这个自增列的值由数据库生成插入后需要回填。这两个配置是最低要求少一个都会让你怀疑人生。protected override void OnModelCreating(ModelBuilder modelBuilder) { // 显式指定表名和 Schema避免 EF Core 默认映射规则产生大小写不一致 modelBuilder.EntityEmployee() .ToTable(EMPLOYEE, DEMO); modelBuilder.EntityEmployee() .HasKey(e e.Id); // 注意达梦8的自增列必须显式声明 OnAdd否则 SaveChanges 后 Id 不回填 modelBuilder.EntityEmployee() .Property(e e.Id) .ValueGeneratedOnAdd(); }3.2 用 DbConnection 注入方式把 DmConnection 交给 EF CoreDbContext 的构造方式决定了连接串从哪里来。最简单可靠的做法是构造函数接收连接串在OnConfiguring里调用UseDmpublic class DemoContext : DbContext { private readonly string _connStr; public DemoContext(string connStr) { _connStr connStr; } public DbSetEmployee Employees { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); // 如果找不到 UseDm 扩展说明适配组件没引用对回第二章检查程序集 optionsBuilder.UseDm(_connStr); } }这里有个关键点OnConfiguring是 EF Core 的入口之一但它不应该负责解析配置文件。连接串从外部通过构造函数传入好处是单元测试时可以直接传一个测试库连接串不必依赖全局配置。如果你用的是 ASP.NET Core 依赖注入注册方式不变services.AddDbContextDemoContext(options { options.UseDm(Configuration.GetConnectionString(DmDemo)); });注意AddDbContext默认生命周期是 Scoped和 ASP.NET Core 请求作用域一致业务代码里不要在单例服务里注入 DbContext否则会出现跨请求复用 DbContext 的并发问题。这一点和 SQL Server 时代没有任何区别换数据库不换规范。3.3 第一次运行要看三个输出跑通最小链路时控制台程序比 Web 项目更容易聚焦问题。下面这段代码完成“插入一条员工记录并打印数据库生成的自增 ID”using System; class Program { static void Main() { string connStr Server127.0.0.1;Port5236;User IdSYSDBA;PasswordSYSDBA;SchemaDEMO; using var ctx new DemoContext(connStr); var emp new Employee { Name zhangsan, Birthday DateTime.Now, Salary 12500.50m }; ctx.Employees.Add(emp); ctx.SaveChanges(); // 输出一Id 是否正常回填 Console.WriteLine($Inserted Id: {emp.Id}); } }第一次运行只检查三件事。第一emp.Id是否大于 0。如果不大于 0说明 EF Core 没有识别出自增列去检查实体配置里的ValueGeneratedOnAdd()。第二有没有抛“表或视图不存在”。抛了就去核对 Schema 是 DEMO、表名是否大写。第三插入后再次查询能查到。做到这三点EF Core 到达梦8 的最小链路才算真正打通。不要小看这个最小例子它把问题分层得清清楚楚连接串错TryOpenDmConnection能提前拦下结构对但 SQL 生成错会在这里暴露映射对但数据读写错则要进入第四章的 CRUD 细节。我在达梦改造项目里发现很多团队把大量时间耗在排错上就是因为没有一个能稳定复现的最小 Demo一上来就调试整个业务的查询链路。4. 增删改查落地达梦8 和 SQL Server 思维不同的四个地方4.1 自增列和 SaveChanges 回填达梦8 支持自增列语法但 EF Core 3.1 对自增列的识别依赖 Provider 的元数据描述。如果 Provider 没有正确声明ValueGeneratedOnAdd实体插入后主键值不会回填到属性上表现为emp.Id是 0。这是连接达梦8 后最高频的映射问题之一而且不会在编译期报错而是运行期悄悄丢数据。解决方式在 3.1 已经写了实体配置里显式声明ValueGeneratedOnAdd()。但如果你的项目升级后才出现这个问题还要检查一件事实体属性类型是不是long还是int。达梦8 的BIGINT对应long如果你建表用了BIGINT但实体属性是intEF Core 会尝试做数值转换插入本身没问题但回填时可能发生溢出或类型不匹配。类型映射表建议照着下面这个对达梦8 列类型C# 属性类型备注BIGINTlong自增主键常用INTint常规整数VARCHAR(n)string长度由 n 决定TIMESTAMPDateTime不要用 DATE 映射 DateTimeDECIMAL(p,s)decimal金额字段用这个DOUBLEdouble浮点计算场景如果达梦8 那边表已经建好没法改成IDENTITY而是用了序列加默认值的方案那就需要用HasDefaultValueSql(SELECT 序列名.NEXTVAL FROM DUAL)告诉 EF Core 取序列值。注意这个配置只是解决了 INSERT 语句里不带主键值的问题回填依然依赖数据库端序列实现序列名写错会导致生成 SQL 执行时报找不到序列对象。4.2 时间字段和字符集时间字段在达梦8 上最容易踩的坑是“DATE 不完整”和“TIMESTAMP 精度不一致”。达梦8 的TIMESTAMP精度默认到微秒EF Core 的DateTime精确到 100 纳秒正常情况下插入和查询都能对上。但如果你在兼容 Oracle 的模式下用DATE类型建表部分版本会把时分秒丢掉查询结果里时间变成 00:00:00这种问题极难排查因为它不报错只让数据“看起来不对”。我一般把时间字段的映射规则固定下来数据库一律TIMESTAMPC# 一律DateTime必要时在实体配置里显式写HasColumnType(TIMESTAMP)modelBuilder.EntityEmployee() .Property(e e.Birthday) .HasColumnType(TIMESTAMP);字符集问题在查询阶段不容易暴露往往到写入含中文的数据时才跳出来。症状可能是中文变问号也可能是在 LINQ 里查询中文条件时返回空结果。排查思路很简单先确认库表字符集比如达梦8 建库时选了 UTF-8 还是 GBK再确认连接串有没有指定字符集参数最后查看插入的数据在数据库工具里显示是否正常。三步走完基本能定位是库、连接还是代码的问题。4.3 分页查询的 Take/SkipEF Core 3.1 的Skip/Take翻译成什么 SQL完全由 Provider 决定。SQL Server Provider 生成OFFSET/FETCH达梦8 的 Provider 会按它自己的语法去生成可能是OFFSET/FETCH也可能是ROWNUM包裹的写法取决于达梦服务器参数和版本。这里不要猜直接抓真实 SQL 看第六章会讲抓法。分页有一个通用的性能和正确性约束必须先排序再分页。在达梦8 上尤其如此。如果不加OrderBy同一查询两次执行可能返回不同顺序的结果尤其表数据在并发写入时。我自己遇到过翻页重复和漏数据的问题最后定位就是分页语句里有集合没有排序字段。推荐写法var page await ctx.Employees .Where(e e.Salary 1000) .OrderBy(e e.Id) // 排序字段必须有唯一性主键最稳 .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToListAsync();深分页性能是另一个需要提前做预案的问题。Skip(100000)在 SQL Server 上也慢但达梦8 的优化器对深分页的处理和 Oracle 更接近可能会扫大量数据块。如果业务确实需要跳大页码常见做法是改成基于游标或基于上次最大 ID 的查询方式这个在达梦8 上效果显著但属于额外架构改造不在本篇最小链路范围内。4.4 事务和并发EF Core 的SaveChanges本身就包了一个隐式事务单次调用里多条 INSERT 会一起提交或一起回滚。如果需要跨多次操作维持一致性用显式事务using var ctx new DemoContext(connStr); await using var tx await ctx.Database.BeginTransactionAsync(); try { ctx.Employees.Add(new Employee { Name a, Birthday DateTime.Now, Salary 100 }); ctx.SaveChanges(); ctx.Employees.Add(new Employee { Name b, Birthday DateTime.Now, Salary 200 }); ctx.SaveChanges(); await tx.CommitAsync(); } catch { await tx.RollbackAsync(); throw; }达梦8 的事务行为和 SQL Server 有几个明显区别。第一达梦8 默认隔离级别更接近读提交同一个事务内如果你先 UPDATE 再 SELECT 那条数据看到的是更新后的值但不同会话之间的可见性受隔离级别约束不要套用 SQL Server 的读已提交快照思维。第二达梦8 在兼容 Oracle 模式下的空值排序、字符串拼接等行为会和 SQL Server 不同事务代码里尽量避免依赖这类语义。第三连接串不写 Schema 时两个连接可能默认进到不同的 Schema看起来像“事务里写了查不到”实际查的表根本不是同一张。这个我放到第五章展开它是我见过最容易误判成事务问题的一种情况。5. 连接达梦8 常见问题排查五个必踩的坑5.1 抛“不支持关键字”或连接串异常问题出在 Provider现象代码在UseDm时报错或运行期抛ArgumentException提示连接串里的某个关键字不被支持。原因两种。一种是引用的程序集根本不是达梦8 的 EF Core 适配组件而是某个第三方兼容包它对连接串关键字的解析规则不同。另一种是代码里仍有UseSqlServer的残留调用比如某个仓储类自己 new 了一个SqlConnection或调用了 SQL Server 相关的扩展。解决全局搜索UseSqlServer、SqlConnection、SqlParameter确认整个数据访问层没有 SQL Server Provider 的残留。再用TryOpenDmConnection单独验证连接串本身。如果连接串在 ADO.NET 层能打开但 EF Core 层报关键字不支持那就是 Provider 适配问题回到第二章检查适配组件版本。5.2 表或视图不存在大小写、双引号和 Schema 三连现象表在数据库工具里能查到但 EF Core 查询时报“表或视图不存在”。原因达梦8 默认将不带双引号的标识符转为大写。EF Core 生成的 SQL 默认不带双引号如果你用工具建表时建的是小写表名实际存储在数据字典里的是带双引号的小写表名查询时变成大写就找不到了。同理列名大小写不一致也会报列不存在。解决建表时全部用大写表名和大写列名DbContext 里用ToTable(EMPLOYEE, DEMO)显式指定如果历史表是小写只能在实体配置里用双引号形式显式映射。排查时执行下面这条 SQL直接看数据字典SELECT OWNER, TABLE_NAME FROM ALL_TABLES WHERE TABLE_NAME EMPLOYEE;如果查出来的是带引号的小写名字基本可以断定大小写问题。Schema 不匹配也会报同样的错误连接串里加SchemaDEMO同时确认ALL_TABLES.OWNER是否等于 DEMO。5.3 连接能建立但首次查询很慢驱动装载和客户端配置现象连接串验证通过SELECT 1秒回但 EF Core 第一条查询要等 3 到 5 秒后续查询又恢复正常。原因达梦8 客户端驱动的 x86/x64 架构与应用程序进程不匹配时驱动加载会多做一次重试或回退产生肉眼可见的延迟。另一种常见原因是第一次查询触发了驱动内部的元数据加载如果连接串没配连接池每次重新握手耗时会被放大。解决检查应用程序的进程架构和 DmProvider 的架构是否一致。.NET Core 3.1 默认 AnyCPU在 64 位系统上跑的是 64 位进程如果引用的驱动是 32 位切换到 64 位重新引用。排查时打开进程模块列表看有没有加载 DmProvider。连接池方面测试阶段建议打开Poolingtrue观察现象是否消失性能验收时再把连接池放到压测里一起验证。5.4 插入成功但 ID 回填为 0自增标识没被 EF Core 识别现象SaveChanges不报错数据库里也有了记录但实体的主键属性还是 0。原因实体主键没有配置ValueGeneratedOnAdd()EF Core 认为这个列是应用端生成的插入时不期望数据库返回值。也可能是 Provider 的元数据没把达梦8 的IDENTITY(1,1)识别为自增。解决先补上实体配置ValueGeneratedOnAdd()再确认建表 SQL 用的确实是IDENTITY(1,1)而不是普通列加默认值。如果表已经建成普通列只能用序列方案实体配置写HasDefaultValueSql(SELECT 序列名.NEXTVAL FROM DUAL)同时主键属性不要手动赋值。注意序列方案的回填依赖 SQL 执行后的OUTPUT能力如果 Provider 不支持可能需要改成插入后显式查询一次最新序列值。5.5 事务里查不到数据或内外行为不一致Schema 和隔离级别混淆现象同一个代码流程里INSERT 之后紧跟着 SELECT查不到刚才插入的数据或者两个环境一个正常一个异常。原因最隐蔽的一种是连接串没写Schema不同环境或不同登录用户的默认 Schema 不一样INSERT 进的是 A 模式SELECT 查的是 B 模式当然查不到。另一类是事务隔离级别被代码显式抬得太高达梦8 在可重复读或更高级别下另一个会话提交的数据在当前事务里看不到。解决第一步统一连接串所有环境显式写SchemaDEMO。第二步检查代码里有没有手动设置隔离级别正常业务不要动它达梦8 默认配置已在绝大多数场景下够用。第三步在事务开头打印当前用户和当前模式确认查询目标没有因为登录用户变化而漂移。6. 用 SQL 捕获验证 EF Core 在达梦8 上的真实行为6.1 打开达梦8 的会话级 SQL 日志EF Core 发出什么 SQL不要靠推理直接看数据库收到的真实语句。达梦8 提供了 SQL 日志能力常见做法是在数据库管理工具里打开 SQL 跟踪或日志记录开关让服务器把每个会话执行的 SQL 落盘。打开后重启会话跑一次业务操作在日志目录里就能看到 EF Core 生成的完整 SQL。重点看四件事表名前面有没有带上 Schema 前缀分页语句用的是OFFSET/FETCH还是ROWNUM自增列插入后有没有执行额外的回填语句参数类型和值是否正常。我第一次看到达梦日志里 EF Core 生成的 SQL 时才发现大小写问题比我预想的更隐蔽——它默认把所有列名都带上了双引号而表名又没有这种组合最容易在历史表上翻车。6.2 把基础自检脚本固化到项目里与其每次手动验证不如在改造期写一个小工具项目启动时自动检查连接和关键表。自检逻辑用 ADO.NET 执行不走 EF Core能隔离框架层问题public static bool CheckDmEnvironment(string connStr, out string result) { using var conn new Dm.DmConnection(connStr); conn.Open(); var cmd conn.CreateCommand(); // 同时验证连接可用和 DEMO 模式下员工表存在 cmd.CommandText SELECT COUNT(*) FROM ALL_TABLES WHERE OWNER UPPER(DEMO) AND TABLE_NAME UPPER(EMPLOYEE); int tableCount Convert.ToInt32(cmd.ExecuteScalar()); cmd.CommandText SELECT COUNT(*) FROM DEMO.EMPLOYEE; long rowCount Convert.ToInt64(cmd.ExecuteScalar()); result $TableExists{tableCount 0}, RowCount{rowCount}; return tableCount 0; }这段自检脚本解决两个问题部署到新环境时第一时间确认表结构存在日常排查时快速区分“应用故障”和“环境故障”。我在达梦8 改造中把类似的检查脚本写进了启动流程新环境配置完连接串先跑它通过之后再接业务流量省了至少三轮排查。6.3 一次改造的教训这次 EFCore3.1 连达梦8 的改造里我最后悔的一件事是前期没有把 SQL 日志和调试器并排打开而是盯着实体配置反复怀疑整整浪费了两天。后来把达梦日志打开第一条慢查询 SQL 摆在面前时原因一目了然表名大小写不一致一切都是因为前期低估了 Provider 差异带来的连锁反应。以后凡是换关系型数据库我都会先让新库的 SQL 日志和排查脚本同时就位让证据说话而不是让代码猜谜。希望帮到你。本文还有配套的精品资源点击获取