☰
EF Core 3.1 连接达梦8数据库实战指南与避坑记录
2026/10/22 8:38:13 网站建设 项目流程

简介:这是一份演示 Entity Framework Core 3.1 连接达梦8数据库的完整示例工程,面向需要在 .NET Core 项目中接入国产数据库的开发人员。压缩包内含一个控制台应用项目,通过继承 DbContext 并调用 UseDAMENG 配置连接字符串,演示了实体定义、数据添加与保存等基本 CRUD 操作流程。资源共 83 个文件,以 dll 依赖库、cs 源代码、json 配置文件及 pdb 调试符号为主,另含解决方案文件与工程文件,整体大小仅 1.79MB,结构精简。其中的 Program.cs 和 Class1.cs 展示了具体调用逻辑,NuGet 缓存与项目资产文件则保留了完整的包引用信息,便于离线核对依赖版本。目前已有 179 人学习此资源,适合初学 EF Core 或希望快速验证达梦8 连接方式的开发者参考。解压后直接打开解决方案即可运行示例,是用 ORM 打通国产数据库链路的一份实用入门资料。

1. 从 EF Core 3.1 到达梦 8:这份压缩包解决的核心问题

做过国产化数据库适配的人都知道,真正卡住进度的往往不是业务逻辑改造,而是 ORM 那一层连不上、跑不通。EF Core 3.1 是 .NET Core 3.1 时代最常用的 ORM 版本,至今仍有大量存量项目跑在上面,而达梦 8 作为国产关系型数据库的代表,在政务、金融、电信项目里出现频率极高。这份名为“EFCore3.1EF连接达梦8数据库例子”的压缩包,包含的就是一个完整的 ConsoleApp1 控制台工程,里面走通了从 NuGet 包引入、DbContext 初始化到真实 CRUD 操作的整套链路。我拆完后的结论是:它不是文档,而是可以直接打开、还原、跑通的示例工程。对正在做信创迁移的 .NET 开发者,或者第一次把 EF Core 指向达梦的人,这份资源能省下大量翻官网、试 provider 的时间。

2. 拆包与选型:先看清工程结构,再定 EF Core 3.1 连达梦的技术路线

2.1 压缩包内部结构说明与还原步骤

解压“EFCore3.1EF连接达梦8数据库例子.7z”后,目录里没有多余的文档和花哨的说明,就是一个标准 .NET Core 控制台解决方案的骨架,包含 ConsoleApp1.sln、ConsoleApp1.csproj、Program.cs、Class1.cs,以及 obj、bin、.vs 这些编译缓存目录。在动手改代码之前,先把工程还原到可运行状态,我一般按下面几步走:

# 1. 解压后先删除旧的编译缓存,避免本机环境差异造成还原失败 rm -rf ConsoleApp1/obj ConsoleApp1/bin .vs # 2. 用 dotnet CLI 还原依赖 dotnet restore ConsoleApp1.sln # 3. 确认目标框架与 EF Core 版本 dotnet list ConsoleApp1/ConsoleApp1.csproj package

第 1 步删除 obj、bin 和 .vs 是我个人的习惯,因为压缩包里的 project.assets.json 和 nuget.g.props 是别人机器上生成的,直接还原偶尔会因全局包路径不一致而出现 NU 级告警。第 2 步的 dotnet restore 会读取 ConsoleApp1.csproj 里声明的 PackageReference,把 Microsoft.EntityFrameworkCore、达梦提供的 EF Core provider 等依赖从 NuGet 拉取到本地。第 3 步用于核对实际生效的包版本,特别是确认 EF Core 没有被还原成 3.1 以外的版本。

Class1.cs 这个文件在示例工程里看起来像占位类,但从工程结构看,它很可能被 Program.cs 里的演示逻辑引用。比如,Class1 里可能定义了一个与达梦表对应的实体模型,Program.cs 里则通过 DbContext 对这张表做查询和写入。

2.2 达梦 8 适配 EF Core 的包选型与版本约束

要让 EF Core 3.1 连到达梦 8,首先必须找到达梦官方或社区维护的 ADO.NET Provider 与 EF Core 适配层。端口说明表如下,这是我在拆多个类似工程时整理的常见组合:

表格:EF Core 3.1 连接达梦 8 的常见包选择

包名作用版本建议
DmProvider达梦 8 的 ADO.NET 驱动,底层连接与命令与达梦 8 客户端版本匹配
Microsoft.EntityFrameworkCoreORM 核心库3.1.x
Microsoft.EntityFrameworkCore.Relational关系型数据库功能支撑3.1.x
DAMENG.EntityFrameworkCoreEF Core 的达梦适配层,提供 UseDAMENG 扩展与达梦 8 版本匹配

实际使用中,最关键的约束是 DAMENG.EntityFrameworkCore 这个包对 EF Core 3.1 的兼容性。很多人在第一步就踩坑,装了最新版包但目标框架是 netcoreapp3.1,还原时直接报 NU1701 或版本冲突,原因就是新包已转向支持 EF Core 6/7/8。此时需要固定版本,常见做法是查看达梦数据库安装目录下的驱动文档,或者在 NuGet 页面按 3.1 兼容版本回退。示例工程之所以能跑通,说明包选型上已经确认过版本匹配,直接用即可。

2.3 连接字符串的基本构成与验证步骤

达梦 8 的连接字符串写法上与 SQL Server 有几分神似,但参数名有差异。拆解示例工程中出现的模式,常用键值包括 Server(或 IP)、Port、Database、User ID、Password。因为达梦默认端口是 5236,所以连接串里一般显式写上Port=5236,避免默认端口不一致导致连不上。我建议在空控制台工程里先把连接串打印出来确认无敏感信息泄漏,再进 ORM 层测试:

// Program.cs 片段:验证达梦连接的基础参数 using System; using Dm; var connStr = "Server=127.0.0.1;Port=5236;Database=TESTDB;User ID=SYSDBA;Password=SYSDBA001;"; using (var conn = new DmConnection(connStr)) { try { conn.Open(); Console.WriteLine("达梦8 ADO.NET 连接成功,State=" + conn.State); } catch (Exception ex) { Console.WriteLine("连接失败: " + ex.Message); } }

这段代码先引用了 Dm 命名空间,里面是达梦 ADO.NET 驱动的核心类型。DmConnection 是驱动里对应连接对象的类,与 SqlConnection 的用法完全相同,只是参数名和默认端口不同。Open 方法会真正建立到数据库的 TCP 连接,State 属性可用来确认连接处于打开状态。若在这一步就出现“无法解析服务器名”或“Socket 连接超时”,先去排查网络端口和达梦服务是否监听,别急着怀疑 ORM。

3. 把 DbContext 跑起来:从实体映射到达梦表的写入链路

3.1 定义实体类与 DbContext 子类的关键代码

在示例工程里,Class1.cs 很可能就是承载实体定义的地方。EF Core 连接达梦的基本做法与连接 SQL Server 一致,都是先定义 POCO 实体,再创建继承 DbContext 的上下文类。以达梦 8 中常见的一张业务表为例,实体和上下文的写法如下:

// Class1.cs 中的实体定义,对应达梦库中的某张业务表 public class UserInfo { public int Id { get; set; } public string UserName { get; set; } public int Age { get; set; } } // 自定义 DbContext,必须引用 DAMENG.EntityFrameworkCore 命名空间 public class DamengDbContext : DbContext { public DbSet<UserInfo> UserInfos { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { string connStr = "Server=127.0.0.1;Port=5236;Database=TESTDB;User ID=SYSDBA;Password=SYSDBA001;"; optionsBuilder.UseDAMENG(connStr); } }

这段代码有两个关键点。第一,DbSet 属性名 UserInfos 默认情况下会映射到数据库表名 UserInfos,如果达梦里实际表名是 T_USER_INFO,就必须在 Fluent API 或数据注解里显式指定。第二,UseDAMENG 是达梦 EF Core 适配层对外暴露的扩展方法,只有安装了正确的 DAMENG.EntityFrameworkCore 包后才会出现在智能提示里。OnConfiguring 里写死连接串的做法只适合快速验证,正式工程会改成从配置文件读取。

3.2 增删改查的完整调用流程与 SaveChanges 的行为说明

验证上下文类能正常初始化后,就可以走完整的增删改查链路。示例工程里的 Program.cs 大概率就是干的这件事。把逻辑拆成四步执行,方便观察每一步对达梦库的影响:

// Program.cs 片段:EF Core 3.1 对达梦8的CRUD演示 using (var db = new DamengDbContext()) { // 新增:写入一条用户数据 var user = new UserInfo { UserName = "测试用户", Age = 25 }; db.UserInfos.Add(user); db.SaveChanges(); Console.WriteLine($"新增成功,生成Id={user.Id}"); // 查询:按条件获取 var query = db.UserInfos.FirstOrDefault(u => u.UserName == "测试用户"); Console.WriteLine($"查询结果: Id={query.Id}, Age={query.Age}"); // 更新:修改年龄字段 query.Age = 30; db.SaveChanges(); // 删除:按Id移除 db.UserInfos.Remove(query); db.SaveChanges(); Console.WriteLine("删除完成"); }

这里最值得关注的是 SaveChanges 的调用次数。每调用一次,EF Core 就会生成对应的 INSERT、UPDATE、DELETE 语句并立即提交到达梦数据库。示例中新增后立刻访问 user.Id,这个行为在 SQL Server 上没问题,但达梦下需要确认适配层是否实现了 OUTPUT 子句或序列回填机制,如果没有,Id 可能仍是 0。这一点对后续做批量写入非常重要,我会在第 4 章的避坑部分专门展开。

3.3 实体属性与达梦数据类型的映射规则

达梦 8 与 MySQL、SQL Server 在数据类型命名上不完全一致,所以 EF Core 的迁移或反向工程必须依赖达梦适配层自己的类型映射表。常见映射如下表所示,这份表格也适用于手工建表时的选型参考:

表格:达梦8常用数据类型与C#属性类型对照

达梦8列类型C#属性类型说明
INTint常规整数
BIGINTlong大整数
VARCHAR(n)string变长字符串,注意n为字符数
NUMBER(p,s)decimal精确小数,映射时需指定精度
DATEDateTime日期时间
TIMESTAMPDateTime带小数秒的时间戳
CLOBstring大文本,适合长内容
BLOBbyte[]二进制大对象

如果建表时用了 DECIMAL 而 C# 端定义为 double,EF Core 查询就会出现精度丢失或转换异常。建议的做法是:实体属性全部遵循表结构本身,比如金额一律用 decimal,并且通过 Fluent API 显式声明 HasColumnType。例如modelBuilder.Entity<UserInfo>(e => { e.Property(p => p.Age).HasColumnType("INT"); });,这样每次迁移或建库不会因为默认类型推断造成偏差。

4. 避坑排查:从“联不通”到“数据错”的四个常见问题与处理

4.1 时间格式与大小写敏感造成查询结果不对

现象:用 EF Core 查询 Date 字段时,明明库里有一条记录,条件值也一致,却查不到数据;或者获取到的 DateTime 与库里存储的不一致。

原因:达梦 8 的 DATE 类型在会话级日期格式影响下,字符串隐式转换时可能出现歧义,比如 2024-01-03 会被解析成 1 月 3 日还是 3 月 1 日,取决于达梦的 DATE_FORMAT 参数。另一个常见点是大小写敏感:达梦库若开启大小写敏感,表名或列名的引号策略不同,EF Core 生成 SQL 时没加双引号,就会因为标识符大小写不匹配而找不到列。

解决:统一在连接串中设置格式化参数,或者在 OnModelCreating 里用 HasColumnName 与达梦实际列名严格对齐。查询时间字段时,尽量用DateTime.Today配合范围比较来替代相等的字符串拼接,减少对隐式转换的依赖。我曾在某次适配里排查了半小时,结果是达梦的列名是 CYR 开头大写,EF Core 默认用了小写且加了双引号,重建模型映射后立刻正常。

4.2 自增主键获取不到回填值

现象:SaveChanges 成功后,实体主键属性仍是默认值 0,而数据库里数据确实写进去了。

原因:EF Core 3.1 在 SQL Server 下通过 IDENTITY 或 OUTPUT 子句获取自增值,但达梦适配层对自增列的元数据识别可能走的不是同一套路径,或者实体配置主键时没有指定 ValueGeneratedOnAdd,导致 EF Core 认为该字段不参与生成。

解决:在实体主键上显式标注或配置自增属性,代码写法如下:

// Class1.cs 中新增的配置:显式声明主键自增 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<UserInfo>(entity => { entity.HasKey(e => e.Id); entity.Property(e => e.Id) .ValueGeneratedOnAdd(); }); }

ValueGeneratedOnAdd 明确告诉 EF Core 这个列在插入时由数据库生成值,适配层才会尝试从达梦序列或自增机制中取值并回填到实体。如果配置后仍拿不到,就退回到手动查询 DmSequence 的方式,在 SaveChanges 后主动查询一次当前的序列值。

4.3 达梦连接数占满导致 EF Core 操作卡死

现象:程序运行几个小时后,EF Core 首次查询或写入特别慢,甚至直接超时,报“连接池已满”或“无法获取连接”。

原因:DbContext 在示例工程里用了 using 包裹,理论上会释放连接,但如果外层代码持有 DbContext 实例未释放,或者 OnConfiguring 每次 new 一个 options,连接池内连接数被撑满。达梦 8 默认连接数配置不高,特别是在开发用的单机版上。

解决:先确认代码里确实用了 using 或 try/finally 释放 DbContext;其次把连接字符串中的 PoolSize 适当调低,例如PoolSize=10;;最后在达梦服务器端查询当前会话数,必要时调整 MAX_SESSIONS 参数。这类问题属于黑匣子型故障,不看到达梦的会话视图,光靠 EF Core 侧日志很难定位。

4.4 使用 Include 加载关联表时生成低效 SQL

现象:两张表建立导航属性后,用 Include 查询只取 20 条数据,但达梦会话监控里看到扫描了整张关联表。

原因:达梦适配层对导航属性的处理不一定生成 LEFT JOIN,可能退化成 N+1 次查询。EF Core 3.1 的查询管道里,如果适配层没有实现拆分查询所需的必要接口,就会走最保守的逐个加载。

解决:先检查生成的 SQL,把 DbContext 的日志打开。然后明确要查的数据量,如果只取少量行,使用显式 Join 并 Select 投影列的方式效率更高。示例工程里如果只是单表操作,就尽量不引入 Include,避免性能误导。

5. 工程化改造:从示例控制台到可维护的数据库访问层

5.1 连接字符串迁移到 appsettings.json 并启用配置绑定

示例工程里的连接串是硬编码在 DbContext 的 OnConfiguring 里,这种方式一旦换环境就要改代码重新编译。把它挪到配置文件是改造第一步,实际操作非常简单。先手动创建 appsettings.json,然后在 Program.cs 里通过 ConfigurationBuilder 读取:

// 引入 Microsoft.Extensions.Configuration 与 Microsoft.Extensions.Configuration.Json using Microsoft.Extensions.Configuration; using System.IO; var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: false) .Build(); var connStr = config.GetConnectionString("DamengConn");

对应的 appsettings.json 内容如下:

{ "ConnectionStrings": { "DamengConn": "Server=127.0.0.1;Port=5236;Database=TESTDB;User ID=SYSDBA;Password=SYSDBA001;PoolSize=10;" } }

注意,AddJsonFile 的 optional 参数建议设为 false,这样配置文件缺失时程序直接失败,而不是默默拿个空串去连数据库,报出让人困惑的“用户名或密码错误”,这类问题浪费过我不止一次时间。

5.2 日志接入与 SQL 输出配置

排查 EF Core 问题最管用的手段就是看它生成的 SQL。在 OnConfiguring 里加一行日志配置即可:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseDAMENG(connStr) .UseLoggerFactory(DbLoggerFactory) .EnableSensitiveDataLogging(false); }
// 定义一个全局可用的日志工厂 public static readonly ILoggerFactory DbLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole() .AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Information); });

这里 EnableSensitiveDataLogging 设置为 false 是安全习惯,避免查询参数里的真实值被日志统统打印出来。线上环境排查时再临时开启,并确保日志文件权限受控。AddFilter 过滤条件只保留 Command 级别的日志,避免输出大量模型构建细节。看到生成的 SQL 后,配合第 4 章的方法逐条核对,大部分问题都能当场暴露。

5.3 避免长上下文:设计轻量级 DbContext 工厂

控制台示例用单例或每任务 new 一个 DbContext 都行,但真实项目里如果服务是多线程的,不能一个 DbContext 实例跨线程使用。一个合理的做法是定义一个简单的工厂接口:

public interface IDamengDbContextFactory { DamengDbContext Create(); } public class DamengDbContextFactory : IDamengDbContextFactory { private readonly string _connectionString; public DamengDbContextFactory(string connectionString) { _connectionString = connectionString; } public DamengDbContext Create() { var options = new DbContextOptionsBuilder<DamengDbContext>() .UseDAMENG(_connectionString) .Options; return new DamengDbContext(options); } }

这个工厂模式把连接串传递和 options 构建解耦。每次要操作数据库时,通过工厂创建新上下文,用完即释放,避免上下文随应用常驻导致内存增长。注意,工厂本身可以是单例,因为它只保存连接串,但每次 Create 返回的 DamengDbContext 必须是新实例。我的习惯是,业务代码只注入工厂,不注入 DbContext 实例。

5.4 用迁移脚本同步表结构时的一个坑

EF Core 的迁移机制依赖 provider 对 Migrations 的支持,达梦适配层是否完全支持 3.1 的迁移操作,不同版本表现不一样。示例工程如果直接跑 dotnet ef migrations add,可能会报“Sequence contains no elements”这类与 provider 能力相关的异常。稳妥做法是:让 DBA 提供建表脚本,实体类由人工或反向工程生成。我在做过的一个模拟项目X中就是这样,达梦表结构由建模工具生成,EF Core 只管读写,迁移只用于开发库,不在生产库执行。

6. 性能排查与索引使用:让达梦 8 上的 EF Core 查询真正快起来

6.1 从达梦数据库自身视角定位慢查询

EF Core 生成的 SQL 是分析性能的起点。常见的排查路径是打开达梦的 SQL 日志或动态视图,找出执行时间最长的语句,然后回到 EF Core 侧看是哪段 LINQ 生成出来的。对比索引和 WHERE 条件,能得出更精确的优化方向。一个典型检查点:达梦 8 的索引建议和 Oracle 更接近,需要关注联合索引前导列是否出现在 WHERE 条件中;如果代码里只按非前导列查询,索引可能完全失效。

6.2 参数化查询与缓存计划的实际影响

EF Core 默认对 LINQ 参数做参数化处理,这是它优于拼接 SQL 的重要一点。在达梦 8 上,参数化语句更容易命中库缓存里的执行计划,减少硬解析开销。反例是直接用 FromSqlRaw 传入拼接字符串,短时间内大量不同值会把共享池撑爆。示例如下:

// 推荐:使用参数化 SQL var name = "测试用户"; var users = db.UserInfos .FromSqlRaw("SELECT * FROM USER_INFOS WHERE USER_NAME = {0}", name) .ToList(); // 不推荐:每次生成不同 SQL 文本 var badUsers = db.UserInfos .FromSqlRaw($"SELECT * FROM USER_INFOS WHERE USER_NAME = '{name}'") .ToList();

两者执行结果一样,但性能差异在多次执行后非常明显。FromSqlRaw 的 {0} 占位符会生成 DbParameter,而拼接写法等于每次传给达梦一个全新语句。对经常执行的查询,统一走参数化不仅能提速,也能降低 SQL 注入风险。

6.3 避免 SELECT * 与过量字段加载

EF Core 的投影只取需要的列,能让达梦减少不必要的 IO。例如:

// 只查询需要的两个字段 var names = db.UserInfos .Where(u => u.Age > 18) .Select(u => new { u.Id, u.UserName }) .ToList();

如果直接db.UserInfos.Where(u => u.Age > 18).ToList(),会把整行所有列都拉出来,即使其中包含 CLOB 大字段,也会显著增加网络传输和内存占用。对于列表页这种一次取几十行的场景,投影列优化是最简单也最见效的手段。

6.4 批量操作注意事项:逐行 SaveChanges 的性能损耗

循环里逐条 Add 再逐个 SaveChanges 是最容易被新手写出来的代码。达梦 8 的数据写入也遵循日志提交开销规律,逐行提交等于把每次网络往返都暴露出来。改进方式是把操作放入一个上下文实例,先 AddRange 再一次性 SaveChanges:

var list = new List<UserInfo>(); for (int i = 0; i < 1000; i++) { list.Add(new UserInfo { UserName = $"用户{i}", Age = 20 + i % 10 }); } db.UserInfos.AddRange(list); db.SaveChanges();

这是在达梦 8 上不借助第三方批量扩展时的合理做法。EF Core 3.1 对大批量插入没有内置的 BulkInsert,如果一次要插几万条,常见做法是分块提交,比如每 500 条 SaveChanges 一次,避免单条 SQL 过长和锁粒度太大。

7. 进阶技巧:在同进程里同时使用 EF Core 与达梦原生 SQL,发挥各自优势

某些业务场景下,ORM 的抽象反而带来限制。比如复杂的层级查询、需要调用达梦存储过程或使用特定方言函数时,直接落地到 ADO.NET 层反而更清晰。我的习惯是建立一套服务,把 EF Core 负责的常规 CRUD 与 DmCommand 负责的特定 SQL 操作放在同一事务里。这样可以在一张达梦业务表中先通过 EF Core 写入主记录,再调用原生命令更新统计字段,二者共享一个连接时,事务边界清晰,数据不会出现一边写入一边失败的割裂状态。

还有一种常见的高频操作是模糊查询。EF Core 的 Contains 会翻译成 LIKE,但若需要按达梦的全文检索能力来做高性能搜索,EF Core 3.1 不一定能覆盖到。此时可以先从达梦动态视图查询符合条件的 Id 列表,再用 EF Core 按 Id 集合加载实体,让 ORM 的导航属性和类型转换优势保留,同时拿到数据库原生能力的性能。

如果把达梦 8 的会话监控打开,能看到 EF Core 生成的 SQL 与原生 SQL 在同一连接上交替执行,事务边界完全一致。这个方法解决了很多适配项目里“数据只能靠 ORM 一条路走”的僵局,也让我在后续几个模拟项目里少改了不少模型代码。从那以后我每次做达梦适配,都会先确认达梦驱动层的原生 API 与 EF Core 在同一个事务里能否协同,再决定模型的边界设计,这套方法也算是我在踩了不少次坑后沉淀出的习惯。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询