资讯详情

资讯详情

EFCore 3.1连接达梦8实战:基于Odbc的实践方案

简介一份基于EF Core 3.1连接达梦8数据库的控制台示例工程适合刚开始接触国产数据库适配的.NET开发者。项目以控制台应用为入口完整演示了从安装DAMENG数据库提供程序、配置服务到创建DbContext上下文、声明DbSet实体映射再到执行增删改查的整个流程。压缩包共83个文件以DLL依赖库、C#源码、JSON配置、PDB调试符号和EXE可执行程序为主整体大小仅1.79MB结构简洁便于按需查看其中已包含编译输出的EXE解压后可在本机快速运行验证。目前已有179人学习下载。源码中重点展示了在OnConfiguring中编写服务器地址、数据库名、用户名与密码等连接字符串参数并用Add方法加入实体、SaveChanges方法提交写入实体类中的属性定义与达梦表字段对应可以帮助理解两者间的类型映射差异。对比示例逐行阅读能少走驱动选择和语法配置的弯路适合作为本地联调或项目迁移的参考模板。1. 当 EFCore 3.1 遇上达梦 8这个例子到底在解决什么拿到EFCore3.1EF连接达梦8数据库例子.7z这个压缩包第一反应是谁还在用 EFCore 3.1达梦 8 不是早就有官方驱动了吗但真正在信创或国产化项目里趟过一遍的人会明白这个例子背后是一个很现实的场景——老项目锁死在 .NET Core 3.1换数据库时发现达梦官方提供的 EF 驱动要么版本对不上要么在 NuGet 源里找不到要么 license 文件折腾半天过不去。这时候最稳的办法反而不是去磕官方驱动而是退回到最原始的 Odbc 方案自己封装一个 Provider。这个压缩包给出的就是一个能跑的答案EFCore 3.1 达梦 8通过System.Data.Odbc当跳板把 EF 的查询表达式翻译成 ODBC 能执行的 SQL。它解决的是「能不能连、能不能跑 CRUD、分页和主键怎么处理」这一类最基础的落地问题。适合谁手上有 .NET Core 3.1 老工程要对接达梦 8、而且不想升级 .NET 版本的人也适合第一次接触达梦、想把连接和基本操作跑通再谈性能优化的人。至于驱动选型、连接串怎么写、哪些坑必须躲开下面按实操顺序拆开讲。2. 达梦 8 的 EF 驱动选型为什么 Odbc 反而是最省事的路径2.1 官方驱动和社区方案的取舍达梦 8 官方提供过一套DmProvider也出过针对 EFCore 的适配包但问题在于它对新版 EFCore 的支持比较积极反而对 3.1 这种已经停止维护的版本态度模糊。实际使用中很常见的现象是装上官方包后UseDm扩展方法确实存在但一执行查询就报ORA-00933之类的 SQL 语法错误或者在AddDbContext时就因为程序集版本冲突直接抛异常。相比之下System.Data.Odbc是 .NET Core 3.1 内置的不依赖任何第三方包。达梦 8 的 ODBC 驱动在 Linux 和 Windows 上都有对应安装包装好后只要能从odbcinst -q -d查到驱动名剩下的就是拼连接串的事。EFCore 3.1 里可以用UseOdbc扩展方法把 DbContext 挂到 ODBC 上这条路不需要改Startup.cs以外的任何基础设施代码。代价是ODBC 层会丢失一部分 EF 的高级能力比如某些查询表达式的翻译、自动建库、迁移。但在「先把业务跑起来」这个阶段这点损失完全可接受。2.2 环境准备清单从 ODBC 驱动到 NuGet 包先把环境列清楚照做就好达梦 8 服务端确保服务已启动能通过dmrman或达梦管理工具连上。默认端口 5236。客户端机器跑 ASP.NET Core 程序的那台安装达梦 8 的 ODBC 驱动。Windows 下安装包解压后运行odbc_install.exeLinux 下用./install.sh。装完用odbcinst -q -d能列出驱动名通常叫DM8 ODBC DRIVER。项目里通过 NuGet 安装三个包Microsoft.EntityFrameworkCore版本必须锁3.1.323.1 的最终补丁Microsoft.EntityFrameworkCore.Relational同版本System.Data.Odbc用5.0.0或更高都行因为 .NET Core 3.1 项目会自动向下兼容。注意这俩 EF 包必须一起升到 3.1.32拆开用容易出现运行时TypeLoadException。安装 ODBC 驱动这个步骤是最容易出玄学问题的地方。Windows 上我遇到过装完驱动odbcad32.exe里能看见但程序一运行报「找不到数据源名称且未指定默认驱动程序」。原因多半是64 位系统里装了 32 位的 ODBC 驱动而 .NET Core 程序跑在 64 位上两边位数对不上。解决办法确认.csproj里PlatformTargetx64/PlatformTarget或者在系统里把 32/64 位两个版本的驱动都装上。3. 把 EFCore 3.1 接到达梦 8连接串、DbConnection 与上下文注册3.1 连接串的写法别用达梦的默认格式很多人第一步就卡在连接串上。达梦管理工具里填的是IP:端口但 ODBC 驱动要求的是ServerIP:端口这种显式写法。我一般这么写Server192.168.1.10:5236;DatabaseDAMENG;UidSYSDBA;PwdSYSDBA001;注意参数DatabaseDAMENG不是数据库名是达梦 8 默认的库标识如果实例改了名字这里要跟着改。Uid和Pwd大小写敏感达梦的SYSDBA默认密码是安装时设置的不是固定的SYSDBA001。这里写DAMENG时注意大小写我见过因为写成Dameng导致 ODBC 报Invalid object name的情况。连接串末尾的分号可加可不加但建议保留。如果程序里还用了DmConnection非 ODBC 方案这套串不通用别混。3.2 注册 DbContext从 ODBC 连接实例化开始EFCore 3.1 下最省事的方式是在Startup.ConfigureServices里手动构造OdbcConnection再把它交给UseOdbc。具体写法public void ConfigureServices(IServiceCollection services) { var connString Server192.168.1.10:5236;DatabaseDAMENG;UidSYSDBA;PwdSYSDBA001;; var connection new OdbcConnection(connString); services.AddDbContextDmDbContext(options options.UseOdbc(connection)); }这里有几个必须注意的点。UseOdbc是Microsoft.EntityFrameworkCore包在 3.1 里提供的扩展方法它接收的是DbConnection实例所以必须先new OdbcConnection再传进去不能直接传字符串。如果直接传字符串EF 会尝试用默认的SqlClient去解析结果就是连接串格式错误、驱动不匹配。关于OdbcConnection生命周期上述写法里AddDbContext默认是Scoped生命周期每次请求都会拿到同一个OdbcConnection。但 ODBC 连接并不都是线程安全的实际项目里我习惯把连接串存配置在OnConfiguring里每请求新建OdbcConnection避免跨请求复用导致连接状态错乱。改成protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var connString Configuration.GetConnectionString(Dm8); optionsBuilder.UseOdbc(new OdbcConnection(connString)); }代价是每次 DbContext 创建都会重新new一个连接对象但对达梦这种本地网络连接的数据库来说开销可忽略。如果你的程序里使用了依赖注入且需要在多个服务里共享同一个 DbContext推荐用前者然后做好Dispose管理。3.3 实体映射规避关键字和类型陷阱实体类写法和平常没有区别但表名和列名要注意。达梦 8 默认开启了 Oracle 兼容模式所以USER、ORDER、SYSDATE、LEVEL这些词是保留字。如果你按 SQL Server 习惯建表表名或列名叫User、OrderEF 生成的查询会直接报 SQL 语法错误。推荐做法实体上显式指定表名和列名避免用类名直接映射。示例[Table(T_SYS_USER)] public class SysUser { [Key] [Column(ID)] public int Id { get; set; } [Column(USER_NAME)] public string UserName { get; set; } [Column(CREATE_TIME)] public DateTime CreateTime { get; set; } }类型对应上达梦的NUMBER类型映射到 C# 的int、long或decimal都可以只要值域匹配。VARCHAR2映射到string没问题但要注意达梦默认用的是VARCHAR2Oracle 兼容不是VARCHAR。DATE类型达梦返回的是DateTimeODBC 驱动会自动转不用手工处理。主键方面达梦 8 支持IDENTITY自增列但默认建表语句常常用的是SEQUENCE。如果你在达梦管理工具里建的表是ID NUMBER GENERATED BY DEFAULT AS IDENTITY那么 EF 插入时就不能给Id赋 0 或 null否则会报非空错误。建议实体主键用int插入时赋 0让数据库自己生成。如果表用的是SEQUENCE需要在OnModelCreating里指定默认值否则插入时 EF 会认为主键已有值而跳过。4. 基于 Odbc Provider 的增删改查从查询翻译到分页手写4.1 查询IQueryable 的常见用法和翻译边界用 Odbc 方案时EF 的IQueryable表达式翻译能力会被削弱。基础查询倒是没问题比如using var db new DmDbContext(); var list db.SysUser .Where(u u.CreateTime DateTime.Today) .OrderByDescending(u u.CreateTime) .Take(10) .ToList();这段代码生成的 SQL 在达梦 8 上能跑因为Where和OrderBy都翻译成了标准 SQL。但如果你在 LINQ 里用了string.ContainsEF 会翻译成WHERE USER_NAME LIKE N%...%达梦的 ODBC 驱动对NCHAR/NVARCHAR的处理有时会出问题表现是查询结果为空但不报错。我踩过的一个实际场景用户搜索名字包含「张」Contains(张)出来 0 条但直接在达梦客户端执行SELECT ... WHERE USER_NAME LIKE %张%却能查到数据。原因排查后确认是 ODBC 驱动对 Unicode 的LIKE匹配走了不同的索引分支。解决办法是用StartsWith或EndsWith做前缀/后缀匹配来绕过但这会牺牲搜索完整性。更稳妥的做法是用EF.Functions.Like显式指定LIKE子句并在连接串里加CharsetGB18030参数。改完后再用Contains就正常了。分页方面EFCore 3.1 的Skip/Take会翻译成FETCH FIRST n ROWS ONLY语法达梦 8 新版本支持但如果你的达梦版本较老比如 8.1需要手动改成ROWNUM方式。常见做法是直接写 SQLvar sql SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER (ORDER BY t.CREATE_TIME DESC) rn FROM T_SYS_USER t ) WHERE rn BETWEEN start AND end; var list db.SysUser .FromSqlRaw(sql, new OdbcParameter(start, start), new OdbcParameter(end, end)) .ToList();注意这里的参数前缀必须用不能用 SQL Server 的以外的形式。达梦 ODBC 的参数占位符是?在FromSqlRaw里传参数时 EF 会根据OdbcParameter自动处理但参数名对不上也会报错。我在项目里惯例是参数名不写前缀直接new OdbcParameter(start, start)然后 SQL 里写start这样在达梦 8.1 和 8.2 上都能跑。4.2 插入与更新不被 EF 的默认行为坑到插入时最核心的一个坑是达梦 8 在 ODBC 模式下EF 的SaveChanges插入语句默认不返回自增主键值。也就是说你插入一条记录后实体的Id属性仍然是 0不是数据库生成的值。这是 ODBC Provider 的通病不是达梦独有的SQL Server 的 ODBC 也这样。原因在于 EF 要拿到自增主键必须执行SELECT SCOPE_IDENTITY()之类的语句而 ODBC 层不保证支持EF 就直接放弃回填了。解决办法是插入完成后重新查询一次var user new SysUser { UserName 张三, CreateTime DateTime.Now }; db.SysUser.Add(user); db.SaveChanges(); // 手动回填主键 var created db.SysUser .Where(u u.UserName 张三 u.CreateTime user.CreateTime) .OrderByDescending(u u.Id) .FirstOrDefault(); if (created ! null) user.Id created.Id;这很粗暴但有效。如果担心并发下查错数据可以在表里加一个唯一业务编号比如用户编号或订单号用这个字段去查。更新和删除的写法和平常一样但要注意达梦的DELETE语句如果涉及带别名或复杂条件的子查询有时会报ORA-00933这时候改成先查Id集合再RemoveRange就能绕开。4.3 事务和批量操作ODBC 模式下的性能与一致性边界EFCore 3.1 下用BeginTransaction是可行的。达梦的 ODBC 驱动支持显式事务SaveChanges默认包在事务里。但你如果用异步事务方法比如await db.Database.BeginTransactionAsync()在 ODBC 模式下有时会抛出InvalidOperationException提示事务已关联到其他连接。原因在于 ODBC 连接切换状态时没有正确传递事务上下文。我一般统一用同步的BeginTransaction()不碰异步版本。批量插入是个大坑。EF 的AddRange在达梦 ODBC 下是一条条插入1000 条数据可能要几十秒。如果业务对写入性能有要求别用 EF 做批量写入直接拼 SQL 走 ADO.NETusing var conn new OdbcConnection(connString); conn.Open(); using var tx conn.BeginTransaction(); var cmd conn.CreateCommand(); cmd.Transaction tx; cmd.CommandText INSERT INTO T_SYS_USER(ID, USER_NAME, CREATE_TIME) VALUES(?, ?, ?); foreach (var item in list) { cmd.Parameters.Clear(); cmd.Parameters.Add(new OdbcParameter { Value item.Id }); cmd.Parameters.Add(new OdbcParameter { Value item.UserName }); cmd.Parameters.Add(new OdbcParameter { Value item.CreateTime }); cmd.ExecuteNonQuery(); } tx.Commit();这段代码用OdbcParameter的Value直接赋值不需要设置DbTypeODBC 驱动会自动推断列类型。唯一的注意点是CREATE_TIME字段如果达梦里定义的是DATE类型DateTime直接传没问题但如果字段是TIMESTAMP在某些达梦版本上需要显式指定OdbcType.DateTime否则可能被转成字符串导致格式错误。5. EFCore 3.1 连接达梦 8 的常见问题避坑现象、原因、解决5.1 启动时报「无法加载 DmProvider文件名或程序集无效」现象程序在AddDbContext或首次查询时报错提示找不到达梦的 Provider 程序集。原因你装了达梦的DmProviderNuGet 包但项目中同时启用了 Odbc 方案EF 启动时扫描到DmProvider又无法正确加载其工厂类导致申请DbProviderFactory失败。这种情况常见于UseOdbc和UseDm同时被引用。解决如果走 Odbc 路线彻底卸载DmProvider相关包。检查.csproj里有没有残留PackageReference IncludeDmProvider ...有就删掉并dotnet restore。如果项目里其它模块硬依赖了DmProvider可以把该模块独立成一个子项目只在那个子项目里用主程序保持纯净。5.2 查询超时默认 30 秒不够用现象EF 查询执行超过 30 秒被强行终止报Timeout expired。原因ODBC Provider 默认命令超时从连接串或环境变量走EF 的一般超时设置比如UseQueryTrackingBehavior不会直接作用到 ODBC 命令上。达梦在复杂JOIN或大结果集ORDER BY场景下一旦优化器抽风很容易超时。解决在连接串末尾加Connection Timeout60;Command Timeout60。注意 ODBC 驱动对这两个参数的支持不同厂商不一样达梦 8 的Command Timeout参数确实有效。如果改了连接串还超时可以在 DbContext 构造函数里手动设置public DmDbContext(DbContextOptionsDmDbContext options) : base(options) { Database.SetCommandTimeout(60); }5.3 达梦是大小写敏感的表和列名不对就报「无效的表名」现象EF 生成的 SQL 里表名、列名跟达梦系统表里存的大小写不一致报ORA-00942: table or view does not exist。原因达梦默认不区分大小写但如果你的表是拿双引号括起来建的它会按原样存储大小写。ODBC 驱动在转换时处理复杂可能把 EF 传过来的小写表名直接映射到了一个不存在的对象上。解决建表时统一用大写表名实体上的Table和Column特性也写大写。如果你改不了既有表就在OnModelCreating里用HasColumnName强制指定精确名称同时在 EF 查询里避免用ToUpper之类的函数做比较否则不会走索引。5.4 达梦 8.1 下Skip/Take分页查询结果错乱现象EF 生成的OFFSET n ROWS FETCH NEXT m ROWS ONLY在达梦 8.1 某版本上执行后返回行数与预期不符有时还会漏行。原因达梦 8.1 对 ANSI 标准的FETCH语法支持存在缺陷内部优化器把OFFSET翻译成了等价的ROWNUM比较时出现误差。8.2 及更高版本修复了。解决检查达梦服务器版本SELECT * FROM v$version;如果小于 8.2把涉及分页的查询改成ROWNUM子查询方式前面 4.1 节给的ROW_NUMBER()版本。如果服务端已经升级但 EF 生成的 SQL 里仍然出现类似问题在OnConfiguring里关掉 EF 的UseRowNumberForPaging该配置在 3.1 里通过UseOdbc的重载可以传入手动改用FromSqlRaw写分页。6. 验证连没连对从 SELECT 1 到集成测试的落地检查法6.1 连接串和 ODBC 驱动是否真的通了先别急着跑 EF用最小手段验证驱动层面是否通。Windows 下打开odbcad32.exe64 位系统用C:\Windows\System32\odbcad32.exe添加系统 DSN 指向达梦 8测试连接成功后再在程序里跑一个最简单查询using var conn new OdbcConnection(connString); conn.Open(); using var cmd conn.CreateCommand(); cmd.CommandText SELECT 1 FROM DUAL; var result cmd.ExecuteScalar(); Console.WriteLine(result);如果这里能输出 1说明 ODBC 驱动和连接串都没问题。如果这里报错别去调 EF 代码纯粹排查驱动和连接串。SELECT 1 FROM DUAL这段在达梦里一定会返回一行FROM DUAL是 Oracle 兼容写法达梦 8 默认支持。如果报ORA-00942说明你的达梦可能没开启 Oracle 兼容尝试去掉FROM DUAL直接SELECT 1有些达梦版本也能跑。6.2 用集成测试确认 EF 的查询翻译没有跑偏驱动通了再上 EF。写一个最基础的集成测试项目Microsoft.NET.Test.Sdkxunit即可。测试用例覆盖三件事插入一条记录、按主键查出来、删掉这条记录。关键代码[Fact] public void Insert_And_Get_ById_Should_Work() { using var db new DmDbContext(); var user new SysUser { UserName 测试用户, CreateTime DateTime.Now }; db.SysUser.Add(user); db.SaveChanges(); var found db.SysUser.FirstOrDefault(u u.UserName 测试用户); Assert.NotNull(found); db.SysUser.Remove(found); db.SaveChanges(); }注意这个测试里我故意没有用自增主键去断言因为前面提过 ODBC 模式不回填主键。断言用的是UserName字段。如果这条用例能过说明增删查三个方向都通。再补一个Contains的测试用例验证连接串里的CharsetGB18030是否解决了 Unicode 匹配问题。6.3 上线前必须做的一件事抓一次 EF 生成的 SQLODBC 方案最大的隐患是 EF 翻译出来的 SQL 在达梦上性能差。上线前用IDbCommandInterceptor或者在OnConfiguring里启用日志把实际执行的 SQL 打出来过一遍。我习惯在Startup里临时加using (var db new DmDbContext()) { var loggerFactory new LoggerFactory(); loggerFactory.AddProvider(new DbLoggerProvider()); db.Database.SetCommandTimeout(10); var sql db.SysUser.Where(u u.CreateTime DateTime.Today).ToQueryString(); Console.WriteLine(sql); }这段代码在 EFCore 3.1 里对应ToQueryString()方法可以拿到最终执行的 SQL 文本。看到LIKE N...这类带N前缀的写法要特别注意达梦 ODBC 对 Unicode 字面量的处理不一致如果线上数据量超过百万这种查询很可能不走索引。从项目实践来看EFCore 3.1 连达梦 8 这条路是走得通的但需要你接受 Odbc 这个中间层带来的约束——参数命名方式要变、分页要留意版本、自增主键回填要手动查、批量写性能要绕道 ADO.NET。我自己的习惯是连接串统一放配置文件并加上CharsetGB18030实体全部大写显式映射所有分页直接走FromSqlRaw事务只用同步版。这套组合用了一年多没翻过车。如果你手头正好接了一个达梦 8 的信创改造项目先别急着从 EF Core 6 往下降这个例子给的方案足够把第一批业务跑起来等跑通了再评估要不要升级 EFCore 版本去用官方的最新驱动。希望帮到你。提示上面所有连接串里的 IP、端口、账号密码都是示例实际使用务必替换。达梦 SYSDBA 的密码在安装时设定忘记密码需要用达梦的dmrman工具重置别在 ODBC 层浪费时间去猜。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →