
简介这份资源面向使用C#与Oracle数据库进行开发的程序员尤其是希望减少手动编写实体类工作量的开发者。它提供了一套基于OracleCodeGenerator的自动生成方案帮助快速构建与Oracle表结构对应的C#实体类并配合Entity Framework完成数据访问层操作。压缩包共62个文件约2.99MB以cs源码、dll类库、xml配置、xaml界面文件为主另含exe可执行程序、ico图标及项目工程文件结构完整可直接运行。已有211人学习下载。资源涵盖连接字符串配置、实体类设计原则、DbContext使用示例以及Repository模式等扩展思路读者可借助生成工具与示例代码快速搭建Oracle数据访问层理解EF与Oracle的集成方式并在此基础上进行性能优化与代码复用。1. C# 连 Oracle 建实体类从手写到自动生成一条能落地的路径很多做 C# 上位机、MES 对接、金蝶云客户端二开的同行第一次接到 Oracle 库的活时都会卡在同一个地方数据库里几十上百张表字段名全是EMP_NO、DEPT_ID这种下划线大写风格而 C# 侧要的是EmpNo、DeptId这样的 PascalCase 属性。手写实体类一张表二十个字段写十张表就是两百行还得对着NUMBER(10,2)、VARCHAR2(50)、DATE一个个映射类型改一次表结构就得回头改一遍代码。这篇讲的就是怎么把这件事从「体力活」变成「可复现的流程」——用 C# 读取 Oracle 的数据字典自动生成实体类再把它接进你的 ORM 或手写 DAL 里。适合正在做 Oracle 相关 C# 项目、被实体类维护拖住的人也适合想搞清楚「自动生成」背后到底靠哪几张系统视图的人。2. 先搞清楚 Oracle 里实体类的数据从哪来2.1 三张系统视图撑起整个生成逻辑Oracle 不会直接告诉你「这张表有哪些列、什么类型、能不能为空」这些信息散落在数据字典视图里。做实体类生成核心就三张视图作用关键字段USER_TABLES当前用户下的表清单TABLE_NAMEUSER_TAB_COLUMNS每张表的列定义COLUMN_NAME、DATA_TYPE、DATA_LENGTH、DATA_PRECISION、DATA_SCALE、NULLABLE、COLUMN_IDUSER_COL_COMMENTS列注释TABLE_NAME、COLUMN_NAME、COMMENTS如果你要跨 schema 生成把USER_换成ALL_再加一个OWNER条件。USER_TAB_COLUMNS里的COLUMN_ID是列的物理顺序生成属性时按它排序能保证实体类字段顺序和建表语句一致后面做INSERT全列插入时不容易错位。DATA_TYPE的值是NUMBER、VARCHAR2、DATE、TIMESTAMP(6)、CLOB、BLOB这类。注意TIMESTAMP(6)带精度后缀判断时不能直接 TIMESTAMP要用StartsWith。NUMBER类型要分三种情况没有精度和标度的是整数DATA_SCALE 0的是long或intDATA_SCALE 0的才是decimal。这一步判断错了金额字段生成成double后面算钱就会出现尾差这是血泪经验。2.2 类型映射表别用 double 接金额C# 和 Oracle 的类型对应关系网上版本很多但真正在项目里能用的就下面这张。我一般会把它写成一个Dictionarystring, string生成时直接查表。Oracle 类型判断条件C# 类型可空写法NUMBER无精度标度int/longint?NUMBERScale 0longlong?NUMBERScale 0decimaldecimal?VARCHAR2/NVARCHAR2/CHAR—stringstringDATE/TIMESTAMP—DateTimeDateTime?CLOB/NCLOB—stringstringBLOB/RAW—byte[]byte[]FLOAT—doubledouble?string和byte[]本身就是引用类型可空与否在 C# 里表现一样不用加?。NULLABLE Y的列值类型要加?否则插入 null 时会抛异常。这个细节在生成模板里用一个三元表达式就能处理。2.3 连接串和权限别用 sys 去读业务表读数据字典用普通业务账号就行只要有SELECT权限。连接串常见写法Data Source(DESCRIPTION(ADDRESS(PROTOCOLTCP)(HOST192.168.1.10)(PORT1521))(CONNECT_DATA(SERVICE_NAMEORCL)));User IdAPPUSER;Passwordyourpwd;如果你用的是 Oracle.ManagedDataAccess不需要装 Oracle 客户端直接 NuGet 引Oracle.ManagedDataAccess.Core就能跑。用System.Data.OracleClient的话那是老版本 .NET Framework 的东西.NET Core 上已经没有了别在这上面浪费时间。连接建好后先跑一句SELECT COUNT(*) FROM USER_TAB_COLUMNS验证权限能出数再往下做。3. 用 C# 读数据字典并生成实体类文件3.1 查询列信息的 SQL 和参数说明先把要生成的表名作为参数传进去避免拼字符串。下面这条 SQL 一次把列名、类型、长度、精度、可空、注释全查出来SELECT c.COLUMN_ID, c.COLUMN_NAME, c.DATA_TYPE, c.DATA_LENGTH, c.DATA_PRECISION, c.DATA_SCALE, c.NULLABLE, cc.COMMENTS FROM USER_TAB_COLUMNS c LEFT JOIN USER_COL_COMMENTS cc ON c.TABLE_NAME cc.TABLE_NAME AND c.COLUMN_NAME cc.COLUMN_NAME WHERE c.TABLE_NAME :TableName ORDER BY c.COLUMN_ID:TableName是 Oracle 的绑定变量写法用OracleCommand.Parameters.Add传值。注意表名在数据字典里是大写如果你传小写查不到统一ToUpper()一下。ORDER BY COLUMN_ID保证属性顺序和表结构一致这个顺序在做INSERT INTO ... VALUES全列插入时很重要。3.2 生成器主流程从表名到 .cs 文件下面这段是核心生成逻辑读一张表拼出实体类字符串写到文件。用StringBuilder拼别用字符串加号几十个字段时性能差别很明显。public void GenerateEntity(string tableName, string outputDir, string namespaceName) { var columns GetColumns(tableName); // 上一步的查询封装 var className ToPascalCase(tableName); // EMP_INFO - EmpInfo var sb new StringBuilder(); sb.AppendLine(using System;); sb.AppendLine(); sb.AppendLine($namespace {namespaceName}); sb.AppendLine({); sb.AppendLine($ public class {className}); sb.AppendLine( {); foreach (var col in columns) { var propName ToPascalCase(col.ColumnName); var csType MapToCSharpType(col); var nullable col.Nullable Y IsValueType(csType) ? ? : ; if (!string.IsNullOrEmpty(col.Comments)) sb.AppendLine($ /// summary{col.Comments}/summary); sb.AppendLine($ public {csType}{nullable} {propName} {{ get; set; }}); sb.AppendLine(); } sb.AppendLine( }); sb.AppendLine(}); var filePath Path.Combine(outputDir, className .cs); File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8); }ToPascalCase负责把EMP_NO转成EmpNo规则是按_分割每段首字母大写再拼接。MapToCSharpType就是 2.2 那张表的代码化。IsValueType判断是不是int、long、decimal、DateTime、double这些是的话才加?。注释直接写进 XML docVisual Studio 里悬停能看到比没有注释强太多。3.3 批量生成一次把整个 schema 跑完单张表生成只是验证逻辑真正用的时候是整库跑。先查USER_TABLES拿到所有表名循环调用上面的方法public void GenerateAll(string outputDir, string namespaceName) { var tables new Liststring(); using (var cmd new OracleCommand(SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME, conn)) using (var reader cmd.ExecuteReader()) { while (reader.Read()) tables.Add(reader.GetString(0)); } foreach (var t in tables) { try { GenerateEntity(t, outputDir, namespaceName); Console.WriteLine($OK: {t}); } catch (Exception ex) { Console.WriteLine($FAIL: {t} - {ex.Message}); } } }每张表单独 try-catch一张表失败不影响后面的。常见失败原因是表名或列名是 Oracle 保留字比如ORDER、GROUP、LEVEL生成的属性名和 C# 关键字冲突。解决办法是在ToPascalCase里维护一个关键字列表命中就加前缀或者加_后缀。这个坑不提前处理编译时一片红。4. 生成完的实体类怎么接进项目4.1 手写 DAL 场景参数化插入和查询实体类生成出来只是第一步真正要用起来得配一套增删改查。手写 DAL 的话插入语句用绑定变量别拼字符串public void Insert(EmpInfo emp) { var sql INSERT INTO EMP_INFO (EMP_NO, EMP_NAME, HIRE_DATE, SALARY) VALUES (:EmpNo, :EmpName, :HireDate, :Salary); using (var cmd new OracleCommand(sql, conn)) { cmd.Parameters.Add(:EmpNo, OracleDbType.Varchar2).Value emp.EmpNo; cmd.Parameters.Add(:EmpName, OracleDbType.Varchar2).Value emp.EmpName; cmd.Parameters.Add(:HireDate, OracleDbType.Date).Value emp.HireDate; cmd.Parameters.Add(:Salary, OracleDbType.Decimal).Value emp.Salary; cmd.ExecuteNonQuery(); } }参数名和实体属性名对应OracleDbType要和列类型匹配。decimal对应OracleDbType.DecimalDateTime对应OracleDbType.Datestring对应Varchar2。类型传错时 Oracle 会隐式转换大部分情况能过但DATE传成Varchar2时格式不对就报ORA-01861这种错误排查起来很费时间。4.2 ORM 场景Dapper 和 EF Core 的取舍如果项目里已经用了 Dapper实体类直接就能用列名和属性名不一致时在查询里写别名或者配一个SqlMapper.SetTypeMap。Dapper 轻启动快适合上位机这种对启动时间敏感的场景。EF Core 的话Oracle 官方有Oracle.EntityFrameworkCore包实体类配[Column(EMP_NO)]特性或者用 Fluent API 映射。EF Core 的好处是能自动生成 SQL坏处是复杂查询翻译出来的 SQL 有时候很离谱oracle分页在 EF Core 里默认用ROWNUM嵌套性能不如手写OFFSET FETCH。我的习惯是简单 CRUD 用 EF Core复杂报表查询走 Dapper 或手写 SQL两套并存不冲突。4.3 生成器本身的维护模板和配置分离生成器写死类型映射和命名规则后面改起来麻烦。我一般把映射表、命名空间、输出目录、忽略表清单放到一个appsettings.json里生成器读配置。这样换一个项目改配置文件就行代码不用动。忽略表清单用来跳过TEMP_、BAK_这类临时表不然生成一堆没用的类。{ Namespace: MyApp.Entity, OutputDir: ./Entities, IgnorePrefix: [TEMP_, BAK_, LOG_], TypeMapping: { NUMBER_SCALE0: long, NUMBER_SCALEGT0: decimal } }配置化之后生成器就变成一个通用工具团队里谁都能用不用每个人都去改代码。5. 避坑生成实体类时最容易翻车的几个地方5.1 现象生成的 decimal 属性插入时精度丢失原因NUMBER类型判断时只看DATA_TYPE没看DATA_SCALE把NUMBER(10,2)映射成了long或double。double本身有精度问题long直接把小数截掉。解决在MapToCSharpType里严格按DATA_SCALE分支Scale 0一律decimal。生成后抽查几张有金额字段的表确认属性类型是decimal。5.2 现象表名或列名是 Oracle 保留字生成后编译报错原因ORDER、GROUP、LEVEL、COMMENT这些词在 C# 里是关键字或上下文关键字直接做属性名编译不过。解决维护一个 C# 关键字列表ToPascalCase之后检查命中就加前缀。Order在 C# 里是合法标识符用起来和普通属性一样。5.3 现象TIMESTAMP(6)类型没匹配上生成了 string原因DATA_TYPE返回的是TIMESTAMP(6)代码里写的是 TIMESTAMP精确匹配失败。解决类型判断用StartsWith(TIMESTAMP)DATE同理。生成后 grep 一下有没有string类型的日期字段有就说明匹配漏了。5.4 现象跨 schema 生成时查不到表原因用了USER_TABLES只能看到当前登录用户下的表。要生成其他 schema 的表得用ALL_TABLES加OWNER条件。解决把视图名和OWNER做成参数USER_和ALL_按需切换。用ALL_时记得加WHERE OWNER :Owner不然会把整个库的表都拉出来。5.5 现象生成的文件编码不对中文注释乱码原因File.WriteAllText默认用 UTF-8 无 BOM某些老版本 Visual Studio 打开时按 GBK 解析注释就乱了。解决显式指定new UTF8Encoding(true)带 BOM或者统一团队编辑器编码设置。生成后打开一个带中文注释的文件确认一下别等提交了才发现。6. 进阶把生成器做成能持续用的工具生成器跑通一次不难难的是表结构变了之后能快速重新生成、且不覆盖手写代码。我的做法是生成两个文件一个EmpInfo.cs只放属性每次重新生成直接覆盖另一个EmpInfo.Ext.cs用partial class放手写的方法和扩展逻辑生成器不碰它。这样表加字段时重新跑一遍属性自动更新手写代码原封不动。验证生成结果是否可靠我一般做三件事。第一拿生成后的实体类对一个真实查询做映射看字段值能不能正确读出来特别是decimal和DateTime。第二对一张有数据的表做全列INSERT再SELECT回来比对每个字段的值确认没有类型转换丢失。第三把生成器对同一张表跑两次用 diff 比对输出确认是幂等的——如果两次结果不一样说明排序或命名逻辑有随机性得修。再进一步可以把生成器做成一个控制台工具加个--watch参数监听表结构变化或者集成到 CI 里每次数据库变更后自动重新生成实体类并提交。这样团队里没人需要手动维护实体类新加一张表跑一下工具类就出来了。我自己在这个事情上踩过最大的坑是早期图省事把类型映射写成了if-else硬编码后来项目从 Oracle 换到另一个库整个生成器重写了一遍。从那以后凡是生成器我都把「读元数据」和「拼代码」分成两层中间用一张配置表隔开。换库时只改读元数据那层拼代码的逻辑不动。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取