> 技术文档 > 随笔20250721 PostgreSQL实体类生成器

随笔20250721 PostgreSQL实体类生成器

我来帮你创建一个C#程序,从PostgreSQL数据库读取表结构并生成对应的实体类文件。我已经创建了一个完整的PostgreSQL实体类生成器。这个程序包含以下主要功能:

主要特性

  1. 数据库连接: 使用Npgsql连接PostgreSQL数据库
  2. 表结构读取: 自动读取所有表的结构信息
  3. 类型映射: 将PostgreSQL数据类型映射为C#数据类型
  4. 实体生成: 为每个表生成对应的C#实体类
  5. 文件导出: 将生成的类保存到指定文件夹

使用方法

  1. 安装NuGet包:

    Install-Package Npgsql
  2. 配置连接字符串: 修改Main方法中的连接字符串:

    var connectionString = \"Host=localhost;Database=your_database;Username=your_username;Password=your_password;\";
  3. 设置输出路径:

    var outputPath = @\"C:\\GeneratedEntities\";
  4. 运行程序: 执行后会自动生成所有表的实体类

生成的实体类特点

  • 包含完整的Data Annotations特性
  • 主键字段标记为[Key]
  • 字段映射使用[Column]特性
  • 支持可空类型
  • 字符串长度限制
  • 帕斯卡命名法(下划线转换)

支持的数据类型

  • 整数类型:int, long, short
  • 浮点类型:float, double, decimal
  • 文本类型:string
  • 日期时间:DateTime, DateTimeOffset, TimeSpan
  • 其他:bool, Guid, byte[]

生成的实体类可以直接用于Entity Framework Core或其他ORM框架。

1.try cath 写在哪里? 写在 ViewModel层 Servace层最多使用自定义异常业务异常进行处理(传给ViewModel层处理), DAO层进行数据库操作流水账式,但是要做好资源释放操作2.DbHelp 是什么模式?单例?释放需要手动释放?单例模式(和项目的整个声明周期一起,关了就完蛋了),不需要关闭(和项目的整个声明周期一起,关了就完蛋了),无法批量插入是因为 表结构还没完全构建完成,解决方法,先插入第一条来“预热”连接,如果还有后续的数据,就直接进行批量插入.public async Task InitTestOrdersAsync(){await _db.DeleteTableAsync();await _db.CreateTableAsync();var mockOrders = new List();// ... (生成 mockOrders 的代码) ...if (mockOrders.Any()){// 1. 先插入第一条来“预热”连接await _db.InsertAsync(mockOrders.First());// 2. 再批量插入剩下的数据var remainingOrders = mockOrders.Skip(1).ToList();if(remainingOrders.Any()){await _db.InsertAllAsync(remainingOrders);}}}3.弄清 异步和非异步方法的区别,以及使用场景只要底层(DAO 和 HttpClient)是异步的,Service 层 和 ViewModel 层也都必须是异步的!也就是说:要一路 async 到顶层。| 层级 | 是否 async | 原因  || ----------------------------- | -------- | --------------------- || DAO(使用 SQLiteAsyncConnection) | ✅ 必须异步 | SQLite 是 IO,使用异步 API || 调用 DAO 的 Service  | ✅ 必须异步 | 调用异步方法 → 必须用 `await` || 调用 Service 的 ViewModel | ✅ 必须异步 | 继续 `await` service 结果 || 情况  | 是否 async || ---------------- | ----------- || 方法内部没有任何异步调用 | ❌ 不需要 async || 方法只是封装计算逻辑、纯内存操作 | ❌ 不需要 async |你用的是 SQLiteAsyncConnection + HttpClient 这样的异步库,整个调用链(DAO → Service → ViewModel)都必须使用 async/await 才是正确做法。🎯 为什么 .Result 或 .Wait() 会“把线程卡住”?原因是:.Result 和 .Wait() 是同步阻塞方法,会强制当前线程“原地等到任务完成”,不能做别的事,也不会释放线程。4.创建一个读取postSQL数据库的工具,生成实体类5432

PostgreSQLEntityGenerator.cs

using System;using System.Collections.Generic;using System.Data;using System.IO;using System.Text;using Npgsql;namespace ConsoleApp1{ public class EntityGenerator { private readonly string _connectionString; private readonly string _outputPath; public EntityGenerator(string connectionString, string outputPath) { _connectionString = connectionString; _outputPath = outputPath; } public void GenerateEntities() { try { // 确保输出目录存在 if (!Directory.Exists(_outputPath)) {  Directory.CreateDirectory(_outputPath); } using (var connection = new NpgsqlConnection(_connectionString)) {  connection.Open();  // 获取所有表名  var tables = GetTableNames(connection);  foreach (var tableName in tables)  { Console.WriteLine($\"正在生成实体类: {tableName}\"); GenerateEntityForTable(connection, tableName);  } } Console.WriteLine($\"实体类生成完成!输出路径: {_outputPath}\"); } catch (Exception ex) { Console.WriteLine($\"生成实体类时发生错误: {ex.Message}\"); throw; } } private List GetTableNames(NpgsqlConnection connection) { var tables = new List(); const string sql = @\" SELECT table_name  FROM information_schema.tables  WHERE table_schema = \'public\'  AND table_type = \'BASE TABLE\' ORDER BY table_name\"; using (var command = new NpgsqlCommand(sql, connection)) using (var reader = command.ExecuteReader()) { while (reader.Read()) {  tables.Add(reader.GetString(\"table_name\")); } } return tables; } private void GenerateEntityForTable(NpgsqlConnection connection, string tableName) { var columns = GetTableColumns(connection, tableName); var entityCode = GenerateEntityCode(tableName, columns); var fileName = $\"{ToPascalCase(tableName)}.cs\"; var filePath = Path.Combine(_outputPath, fileName); File.WriteAllText(filePath, entityCode, Encoding.UTF8); } private List GetTableColumns(NpgsqlConnection connection, string tableName) { var columns = new List(); const string sql = @\" SELECT  c.column_name,  c.data_type,  c.is_nullable,  c.column_default,  c.character_maximum_length,  c.numeric_precision,  c.numeric_scale,  CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false  END as is_primary_key FROM information_schema.columns c LEFT JOIN (  SELECT ku.column_name  FROM information_schema.table_constraints tc  JOIN information_schema.key_column_usage ku ON tc.constraint_name = ku.constraint_name AND tc.table_schema = ku.table_schema  WHERE tc.constraint_type = \'PRIMARY KEY\' AND tc.table_name = @tableName AND tc.table_schema = \'public\' ) pk ON c.column_name = pk.column_name WHERE c.table_name = @tableName  AND c.table_schema = \'public\' ORDER BY c.ordinal_position\"; using (var command = new NpgsqlCommand(sql, connection)) { command.Parameters.AddWithValue(\"@tableName\", tableName); using (var reader = command.ExecuteReader()) {  while (reader.Read())  { columns.Add(new ColumnInfo { Name = reader.GetString(\"column_name\"), DataType = reader.GetString(\"data_type\"), IsNullable = reader.GetString(\"is_nullable\") == \"YES\", DefaultValue = reader.IsDBNull(\"column_default\") ? null : reader.GetString(\"column_default\"), MaxLength = reader.IsDBNull(\"character_maximum_length\") ? (int?)null : reader.GetInt32(\"character_maximum_length\"), Precision = reader.IsDBNull(\"numeric_precision\") ? (int?)null : reader.GetInt32(\"numeric_precision\"), Scale = reader.IsDBNull(\"numeric_scale\") ? (int?)null : reader.GetInt32(\"numeric_scale\"), IsPrimaryKey = reader.GetBoolean(\"is_primary_key\") });  } } } return columns; } private string GenerateEntityCode(string tableName, List columns) { var sb = new StringBuilder(); var className = ToPascalCase(tableName); // 添加using语句 sb.AppendLine(\"using System;\"); sb.AppendLine(\"using System.ComponentModel.DataAnnotations;\"); sb.AppendLine(\"using System.ComponentModel.DataAnnotations.Schema;\"); sb.AppendLine(); // 添加命名空间 sb.AppendLine(\"namespace Entities\"); sb.AppendLine(\"{\"); // 添加Table特性 sb.AppendLine($\" [Table(\\\"{tableName}\\\")]\"); sb.AppendLine($\" public class {className}\"); sb.AppendLine(\" {\"); // 生成属性 foreach (var column in columns) { GenerateProperty(sb, column); } sb.AppendLine(\" }\"); sb.AppendLine(\"}\"); return sb.ToString(); } private void GenerateProperty(StringBuilder sb, ColumnInfo column) { var propertyName = ToPascalCase(column.Name); var csharpType = GetCSharpType(column); // 添加注释 sb.AppendLine(); sb.AppendLine($\" /// \"); sb.AppendLine($\" /// {column.Name}\"); sb.AppendLine($\" /// \"); // 添加特性 if (column.IsPrimaryKey) { sb.AppendLine(\" [Key]\"); } sb.AppendLine($\" [Column(\\\"{column.Name}\\\")]\"); if (!column.IsNullable && csharpType != \"string\") { sb.AppendLine(\" [Required]\"); } if (column.MaxLength.HasValue && csharpType == \"string\") { sb.AppendLine($\" [MaxLength({column.MaxLength.Value})]\"); } // 生成属性 sb.AppendLine($\" public {csharpType} {propertyName} {{ get; set; }}\"); } private string GetCSharpType(ColumnInfo column) { var baseType = column.DataType.ToLower() switch { \"integer\" or \"int4\" => \"int\", \"bigint\" or \"int8\" => \"long\", \"smallint\" or \"int2\" => \"short\", \"boolean\" or \"bool\" => \"bool\", \"real\" or \"float4\" => \"float\", \"double precision\" or \"float8\" => \"double\", \"numeric\" or \"decimal\" => \"decimal\", \"text\" or \"varchar\" or \"character varying\" or \"char\" or \"character\" => \"string\", \"timestamp\" or \"timestamp without time zone\" => \"DateTime\", \"timestamp with time zone\" or \"timestamptz\" => \"DateTimeOffset\", \"date\" => \"DateTime\", \"time\" or \"time without time zone\" => \"TimeSpan\", \"uuid\" => \"Guid\", \"json\" or \"jsonb\" => \"string\", \"bytea\" => \"byte[]\", _ => \"object\" }; // 如果字段可为空且不是引用类型,添加? if (column.IsNullable && baseType != \"string\" && baseType != \"byte[]\" && baseType != \"object\") { return baseType + \"?\"; } return baseType; } private string ToPascalCase(string input) { if (string.IsNullOrEmpty(input)) return input; var parts = input.Split(\'_\', \'-\'); var sb = new StringBuilder(); foreach (var part in parts) { if (!string.IsNullOrEmpty(part)) {  sb.Append(char.ToUpper(part[0]));  if (part.Length > 1) sb.Append(part[1..].ToLower()); } } return sb.ToString(); } } public class ColumnInfo { public string Name { get; set; } public string DataType { get; set; } public bool IsNullable { get; set; } public string DefaultValue { get; set; } public int? MaxLength { get; set; } public int? Precision { get; set; } public int? Scale { get; set; } public bool IsPrimaryKey { get; set; } }}

Program.cs

using ConsoleApp1;try{ // 配置数据库连接字符串 var connectionString = \"Host=localhost;Database=postgres;Username=postgres;Password=123456;\"; // 配置输出路径 var outputPath = @\"C:\\MySoft\\PostgreSQL\\Models\"; // 创建生成器实例 var generator = new EntityGenerator(connectionString, outputPath); // 生成实体类 generator.GenerateEntities(); Console.WriteLine(\"按任意键退出...\"); Console.ReadKey();}catch (Exception ex){ Console.WriteLine($\"程序执行出错: {ex.Message}\"); Console.WriteLine(\"按任意键退出...\"); Console.ReadKey();}Console.WriteLine(\"Hello, World!\");