编程与数学 03-008 《看潮企业管理软件》项目开发 07 数据访问 3-3
- 三、数据访问类说明
-
- (一)文件概述
-
- 1.1 基本信息
- 1.2 设计目标
- (二)核心架构
-
- 2.1 类结构图
- 2.2 依赖关系
- (三)核心功能详解
-
- 3.1 数据库连接管理
-
- 连接字段说明:
- 关键方法:
- 3.2 SQL操作接口
-
- 3.2.1 查询类方法
- 3.2.2 执行类方法
- 3.3 DataTable同步机制
-
- 3.3.1 同步方式对比
- 3.3.2 变更检测流程
- 3.3.3 数据类型映射表
- 3.4 错误处理体系
-
- 3.4.1 错误处理流程
- 3.4.2 错误记录字段
- 3.5 参数管理系统
-
- 3.5.1 参数类型
- 3.5.2 参数缓存机制
- 3.5.3 相关方法
- (四)使用示例
-
- 4.1 基本查询
- 4.2 数据更新
- 4.3 多表事务
- 4.4 参数管理
- (五)配置与优化
-
- 5.1 连接字符串配置
- 5.2 性能优化建议
-
- 5.2.1 SQL注入防护
- 5.2.2 连接管理优化
- 5.2.3 批量操作优化
- 5.3 日志配置
- (六)安全注意事项
-
- 6.1 潜在风险
- 6.2 安全建议
- 6.3 审计日志
- (七)扩展与维护
-
- 7.1 扩展建议
- 7.2 维护建议
- 7.3 兼容性说明
- (八)故障排查
-
- 8.1 常见问题
-
- 问题1:连接失败
- 问题2:SQL执行超时
- 问题3:数据同步失败
- 8.2 调试技巧
- (九)总结
-
- 9.1 优点
- 9.2 待改进
- 9.3 适用场景
- 四、数据处理方法
-
- (一)DAO.NET 简介与集成方案
-
- 1.1 什么是 DAO.NET?
- 1.2 与 KcDb 的集成方式
- (二)基础 DAO 类设计
-
- 2.1 基础实体类
- 2.2 基础 DAO 接口
- 2.3 基础 DAO 实现
- (三)具体业务 DAO 实现示例
-
- 3.1 用户实体和DAO
- 3.2 产品实体和DAO
- (四)DAO工厂和服务层
-
- 4.1 DAO工厂
- 4.2 服务层示例
- (五)使用示例
-
- 5.1 基本使用
- 5.2 CRUD操作示例
- 5.3 事务处理示例
- 全文总结
摘要:本文档详细阐述了《看潮企业管理软件》中基于.NET Framework与PostgreSQL的数据访问层实现方案。首先介绍了使用Npgsql驱动连接PostgreSQL数据库的配置方法,包括连接字符串设置和基本CRUD操作。核心部分是KcDb.cs静态类的完整实现,封装了数据库连接管理、SQL执行、DataTable同步、参数管理和错误日志等功能。文档进一步展示了基于DAO.NET模式的基础DAO类设计,包括泛型接口、实体映射、分页查询和批量操作,并提供了用户、产品等业务DAO的具体实现示例,形成了一套完整的分层数据访问架构。
关键词:数据访问层、PostgreSQL、Npgsql、DAO模式、.NET Framework、KcDb静态类、实体映射、分层架构、ERP系统
人工智能助手:DeepSeek、Kimi
三、数据访问类说明
(一)文件概述
1.1 基本信息
- 文件名称:KcDb.cs
- 所属项目:KcErp(ERP系统)
- 命名空间:KcErp
- 类类型:静态类(static class)
- 主要功能:PostgreSQL数据库操作封装
1.2 设计目标
- 提供统一的数据库访问接口
- 简化数据库操作复杂度
- 实现数据表与数据库的同步
- 提供错误处理和日志记录机制
- 支持系统参数管理
(二)核心架构
2.1 类结构图
KcDb (静态类)
├── 数据库连接字段 (13个)
├── 连接管理方法 (4个)
├── 基本SQL操作方法 (5个)
├── DataTable同步方法 (4个+2个私有)
├── 高级数据操作方法 (4个)
├── 日志记录方法 (1个)
├── 错误处理方法 (1个私有)
├── 辅助方法 (1个)
└── 参数管理方法 (5个)
2.2 依赖关系
依赖外部类/组件:
1. Npgsql (PostgreSQL .NET驱动)
2. DevExpress.XtraEditors (消息框组件)
3. KcErp.KcMain (主程序类)
4. System.Data (ADO.NET核心)
5. System.Windows.Forms (剪贴板功能)
(三)核心功能详解
3.1 数据库连接管理
连接字段说明:
| KcCn | NpgsqlConnection | 数据库连接对象 | null |
| SvNAME | string | 服务器地址/名称 | “” |
| SvPORT | string | PostgreSQL端口 | “5432” |
| DBname | string | 数据库名称 | “” |
| SFlj | bool | 连接状态标志 | false |
| LJtime | int | 命令超时时间(秒) | 0 |
关键方法:
DBLJ() – 建立连接
// 动态构建连接字符串并打开连接
// 格式:Host=服务器;Port=端口;Database=库名;Username=用户;Password=密码
DBopen()/DBclose() – 连接状态管理
- 智能打开/关闭连接
- 维护SFlj状态标志
3.2 SQL操作接口
3.2.1 查询类方法
| DBString() | string | 返回单值字符串 | 获取单个字段值 |
| DBInteger() | int | 返回单值整数 | 获取ID、计数等 |
| DtRead() | DataTable | 返回数据表 | 查询多行数据 |
| GetFormula() | string | 计算公式 | 专用计算场景 |
3.2.2 执行类方法
| DBexec() | string | 执行命令返回标量 | INSERT/UPDATE |
| GetJlSql() | void | 记录SQL日志 | INSERT |
| DtSaves() | bool | DataTable保存 | 批量更新 |
3.3 DataTable同步机制
3.3.1 同步方式对比
| DtSaves() | DataAdapter自动更新 | 简单表单 | 中 |
| DtSaven() | 生成SQL批量执行 | 复杂事务 | 高 |
| GetDtSavev() | 指定字段更新 | 部分更新 | 高 |
3.3.2 变更检测流程
DataTable.GetChanges()
├── DataRowState.Deleted → 生成DELETE语句
├── DataRowState.Added → 生成INSERT语句
└── DataRowState.Modified → 生成UPDATE语句
3.3.3 数据类型映射表
| String/Char | varchar/text | 单引号转义 |
| DateTime | timestamp | 格式化为yyyy-MM-dd HH:mm:ss |
| Date | date | 格式化为yyyy-MM-dd |
| Boolean | boolean | true/false |
| 数值类型 | numeric/int | 直接使用 |
3.4 错误处理体系
3.4.1 错误处理流程
异常发生
↓
DtMsg() 被调用
├── 显示用户友好消息
├── 错误信息复制到剪贴板
├── 确保数据库连接正常
└── 记录到x9_errcode表
3.4.2 错误记录字段
| extime | 错误时间 | now() |
| username | 用户信息 | 站点 用户 子系统 |
| exsource | 错误源 | 方法名 |
| exmessage | 错误消息 | “违反唯一约束” |
| exstacktrace | 堆栈跟踪 | 完整堆栈 |
| exsql | SQL语句 | 前500字符 |
3.5 参数管理系统
3.5.1 参数类型
| 数值参数 | szjg | 数值结果 | 整数配置项 |
| 字符参数 | zfjg | 字符结果 | 字符串配置项 |
| 逻辑参数 | ljjg | 逻辑结果 | 布尔配置项 |
3.5.2 参数缓存机制
获取参数流程:
1. 检查pDTCS缓存表
2. 找到 → 返回缓存值
3. 未找到 → 创建默认值记录
4. 添加到缓存并返回
3.5.3 相关方法
| GetSzcs() | 读取 | 数值 | 懒加载 |
| GetZfcs() | 读取 | 字符 | 懒加载 |
| GetLjcs() | 读取 | 逻辑 | 懒加载 |
| SetZfcs() | 写入 | 字符 | 立即更新 |
| SetSzcs() | 写入 | 数值 | 立即更新 |
(四)使用示例
4.1 基本查询
// 获取单个值
string userName = KcDb.DBString("SELECT username FROM users WHERE id=1");
// 获取整数
int userCount = KcDb.DBInteger("SELECT COUNT(*) FROM users");
// 获取数据表
DataTable dtUsers = KcDb.DtRead("SELECT * FROM users WHERE active=true");
4.2 数据更新
// 执行单条SQL
string newId = KcDb.DBexec("INSERT INTO users(name) VALUES('test') RETURNING id");
// 批量更新DataTable
DataTable dtChanges = GetChanges(); // 获取变更数据
bool success = KcDb.GetDtSaves("SELECT * FROM users", dtChanges);
4.3 多表事务
// 同时更新三个相关表
bool result = KcDb.GetDtSaven(
"orders", dtOrders, // 订单表
"order_items", dtItems, // 订单明细
"order_logs", dtLogs, // 订单日志
"status,total", // orders表更新字段
"quantity,price", // order_items表更新字段
"" // order_logs全字段更新
);
4.4 参数管理
// 读取系统参数
int pageSize = KcDb.GetSzcs("PageSize", 20); // 默认20
string theme = KcDb.GetZfcs("Theme", "Default"); // 默认Default
bool autoSave = KcDb.GetLjcs("AutoSave", true); // 默认true
// 更新参数
KcDb.SetSzcs("PageSize", 50);
KcDb.SetZfcs("Theme", "Dark");
(五)配置与优化
5.1 连接字符串配置
// 建议改进:使用连接字符串构建器
var builder = new NpgsqlConnectionStringBuilder
{
Host = SvNAME,
Port = int.Parse(SvPORT),
Database = DBname,
Username = DLmc,
Password = DLmm,
Pooling = true, // 启用连接池
MinPoolSize = 5, // 最小连接数
MaxPoolSize = 100, // 最大连接数
CommandTimeout = LJtime // 命令超时
};
KcCn.ConnectionString = builder.ToString();
5.2 性能优化建议
5.2.1 SQL注入防护
// 当前问题:字符串拼接SQL
// 建议改进:使用参数化查询
string sql = "SELECT * FROM users WHERE name = @name AND age > @age";
using (var cmd = new NpgsqlCommand(sql, KcCn))
{
cmd.Parameters.AddWithValue("@name", userName);
cmd.Parameters.AddWithValue("@age", minAge);
// … 执行
}
5.2.2 连接管理优化
5.2.3 批量操作优化
5.3 日志配置
| SQL日志 | pSFSJ | false | 是否记录SQL |
| 计算日志 | pCSGS | false | 是否记录计算 |
| 日志表 | x9_jlsql | – | SQL日志表 |
| 错误表 | x9_errcode | – | 错误日志表 |
(六)安全注意事项
6.1 潜在风险
6.2 安全建议
6.3 审计日志
- 所有SQL操作可通过x9_jlsql表审计
- 错误信息记录在x9_errcode表
- 支持操作追踪和故障排查
(七)扩展与维护
7.1 扩展建议
7.2 维护建议
7.3 兼容性说明
- .NET版本:.NET Framework 4.7.2
- 数据库:PostgreSQL 9.0+
- Npgsql:Version 4.x
- DevExpress:相关UI组件版本
(八)故障排查
8.1 常见问题
问题1:连接失败
可能原因:
1. 服务器地址/端口错误
2. 数据库名错误
3. 用户名/密码错误
4. 网络不通
5. PostgreSQL服务未启动
排查步骤:
1. 检查SvNAME、SvPORT、DBname
2. 验证DLmc、DLmm
3. 使用pgAdmin等工具测试连接
4. 检查防火墙设置
问题2:SQL执行超时
可能原因:
1. SQL语句效率低
2. 数据量过大
3. 网络延迟
4. 数据库负载高
解决方案:
1. 优化SQL语句,添加索引
2. 分页查询大数据
3. 调整LJtime超时时间
4. 检查数据库性能
问题3:数据同步失败
可能原因:
1. 数据类型不匹配
2. 主键约束冲突
3. 外键约束违反
4. 权限不足
排查方法:
1. 检查DtMsg()错误信息
2. 查看x9_errcode表记录
3. 验证DataTable结构
4. 检查数据库约束
8.2 调试技巧
(九)总结
9.1 优点
9.2 待改进
9.3 适用场景
- 中小型ERP系统
- 传统WinForms应用程序
- 需要快速开发的数据密集型应用
- 已有大量DataTable操作逻辑的系统
四、数据处理方法
使用 DAO.NET 通过 KcDb 静态类进行数据处理。
(一)DAO.NET 简介与集成方案
1.1 什么是 DAO.NET?
DAO.NET(Data Access Objects for .NET)是一种数据访问模式,将数据访问逻辑从业务逻辑中分离出来,提供标准化的数据访问接口。
1.2 与 KcDb 的集成方式
由于 KcDb 已经是静态类,我们可以创建 DAO 层来封装对 KcDb 的调用,实现:
(二)基础 DAO 类设计
2.1 基础实体类
// 基础实体接口
public interface IEntity
{
int Id { get; set; }
DateTime CreateTime { get; set; }
DateTime? UpdateTime { get; set; }
string CreateUser { get; set; }
}
// 基础实体实现
public abstract class BaseEntity : IEntity
{
public int Id { get; set; }
public DateTime CreateTime { get; set; } = DateTime.Now;
public DateTime? UpdateTime { get; set; }
public string CreateUser { get; set; } = KcDb.JLyh;
// 其他公共属性
public bool IsDeleted { get; set; } = false;
public int Version { get; set; } = 1;
}
2.2 基础 DAO 接口
// 泛型 DAO 接口
public interface IBaseDao<T> where T : IEntity
{
// 基本 CRUD 操作
T GetById(int id);
List<T> GetAll();
List<T> GetByCondition(string whereClause, params object[] parameters);
int Insert(T entity);
bool Update(T entity);
bool Delete(int id, bool softDelete = true);
bool Exists(int id);
// 分页查询
PagedResult<T> GetPaged(int pageIndex, int pageSize, string orderBy = "Id");
PagedResult<T> GetPagedWithCondition(int pageIndex, int pageSize,
string whereClause, string orderBy = "Id", params object[] parameters);
// 批量操作
bool BulkInsert(List<T> entities);
bool BulkUpdate(List<T> entities);
}
// 分页结果类
public class PagedResult<T>
{
public List<T> Data { get; set; }
public int TotalCount { get; set; }
public int PageIndex { get; set; }
public int PageSize { get; set; }
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
}
2.3 基础 DAO 实现
// 基础 DAO 实现(抽象类)
public abstract class BaseDao<T> : IBaseDao<T> where T : IEntity, new()
{
// 表名(由子类指定)
protected abstract string TableName { get; }
// 主键字段名
protected virtual string PrimaryKey => "Id";
// 获取数据库连接(通过 KcDb)
protected NpgsqlConnection GetConnection()
{
KcDb.DBopen();
return KcDb.KcCn;
}
// 1. 根据ID获取实体
public virtual T GetById(int id)
{
string sql = $"SELECT * FROM {TableName} WHERE {PrimaryKey} = {id} AND IsDeleted = false";
try
{
DataTable dt = KcDb.DtRead(sql);
if (dt != null && dt.Rows.Count > 0)
{
return MapDataRowToEntity(dt.Rows[0]);
}
return default(T);
}
catch (Exception ex)
{
// 记录日志
LogError($"GetById failed for {TableName}, ID: {id}", ex);
throw;
}
}
// 2. 获取所有记录
public virtual List<T> GetAll()
{
string sql = $"SELECT * FROM {TableName} WHERE IsDeleted = false ORDER BY {PrimaryKey}";
try
{
DataTable dt = KcDb.DtRead(sql);
return MapDataTableToList(dt);
}
catch (Exception ex)
{
LogError($"GetAll failed for {TableName}", ex);
throw;
}
}
// 3. 条件查询
public virtual List<T> GetByCondition(string whereClause, params object[] parameters)
{
string sql = $"SELECT * FROM {TableName} WHERE IsDeleted = false";
if (!string.IsNullOrEmpty(whereClause))
{
sql += $" AND ({whereClause})";
}
// 如果有参数,构建参数化查询(简化版)
if (parameters != null && parameters.Length > 0)
{
// 实际项目中应该使用参数化查询
for (int i = 0; i < parameters.Length; i++)
{
// 简单的参数替换(注意SQL注入风险,实际项目应该用参数化)
sql = sql.Replace($"{{{i}}}", EscapeSqlValue(parameters[i]));
}
}
try
{
DataTable dt = KcDb.DtRead(sql);
return MapDataTableToList(dt);
}
catch (Exception ex)
{
LogError($"GetByCondition failed for {TableName}", ex);
throw;
}
}
// 4. 插入实体
public virtual int Insert(T entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
// 设置创建信息
entity.CreateTime = DateTime.Now;
entity.CreateUser = KcDb.JLyh;
// 构建INSERT SQL
var properties = GetInsertProperties(entity);
string columns = string.Join(", ", properties.Keys);
string values = string.Join(", ", properties.Values);
string sql = $"INSERT INTO {TableName} ({columns}) VALUES ({values}) RETURNING {PrimaryKey}";
try
{
string result = KcDb.DBexec(sql);
if (!string.IsNullOrEmpty(result) && int.TryParse(result, out int newId))
{
entity.Id = newId;
return newId;
}
return 0;
}
catch (Exception ex)
{
LogError($"Insert failed for {TableName}", ex);
throw;
}
}
// 5. 更新实体
public virtual bool Update(T entity)
{
if (entity == null || entity.Id <= 0)
throw new ArgumentException("Invalid entity or ID");
// 设置更新信息
entity.UpdateTime = DateTime.Now;
// 构建UPDATE SQL
var updateFields = GetUpdateProperties(entity);
if (updateFields.Count == 0)
return false;
string setClause = string.Join(", ", updateFields.Select(kv => $"{kv.Key} = {kv.Value}"));
string sql = $"UPDATE {TableName} SET {setClause} WHERE {PrimaryKey} = {entity.Id}";
try
{
KcDb.DBexec(sql);
return true;
}
catch (Exception ex)
{
LogError($"Update failed for {TableName}, ID: {entity.Id}", ex);
throw;
}
}
// 6. 删除实体
public virtual bool Delete(int id, bool softDelete = true)
{
if (id <= 0)
return false;
string sql;
if (softDelete)
{
// 软删除
sql = $"UPDATE {TableName} SET IsDeleted = true, UpdateTime = NOW() WHERE {PrimaryKey} = {id}";
}
else
{
// 硬删除
sql = $"DELETE FROM {TableName} WHERE {PrimaryKey} = {id}";
}
try
{
KcDb.DBexec(sql);
return true;
}
catch (Exception ex)
{
LogError($"Delete failed for {TableName}, ID: {id}", ex);
throw;
}
}
// 7. 检查是否存在
public virtual bool Exists(int id)
{
string sql = $"SELECT COUNT(1) FROM {TableName} WHERE {PrimaryKey} = {id} AND IsDeleted = false";
try
{
int count = KcDb.DBInteger(sql);
return count > 0;
}
catch (Exception ex)
{
LogError($"Exists check failed for {TableName}, ID: {id}", ex);
throw;
}
}
// 8. 分页查询
public virtual PagedResult<T> GetPaged(int pageIndex, int pageSize, string orderBy = "Id")
{
if (pageIndex < 1) pageIndex = 1;
if (pageSize < 1) pageSize = 10;
int offset = (pageIndex – 1) * pageSize;
// 获取总数
string countSql = $"SELECT COUNT(*) FROM {TableName} WHERE IsDeleted = false";
int totalCount = KcDb.DBInteger(countSql);
// 获取分页数据
string dataSql = $@"
SELECT * FROM {TableName}
WHERE IsDeleted = false
ORDER BY {orderBy}
LIMIT {pageSize} OFFSET {offset}";
try
{
DataTable dt = KcDb.DtRead(dataSql);
List<T> data = MapDataTableToList(dt);
return new PagedResult<T>
{
Data = data,
TotalCount = totalCount,
PageIndex = pageIndex,
PageSize = pageSize
};
}
catch (Exception ex)
{
LogError($"GetPaged failed for {TableName}", ex);
throw;
}
}
// 9. 带条件的分页查询
public virtual PagedResult<T> GetPagedWithCondition(int pageIndex, int pageSize,
string whereClause, string orderBy = "Id", params object[] parameters)
{
if (pageIndex < 1) pageIndex = 1;
if (pageSize < 1) pageSize = 10;
int offset = (pageIndex – 1) * pageSize;
// 构建WHERE条件
string where = "IsDeleted = false";
if (!string.IsNullOrEmpty(whereClause))
{
where += $" AND ({whereClause})";
}
// 参数处理
if (parameters != null && parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
{
where = where.Replace($"{{{i}}}", EscapeSqlValue(parameters[i]));
}
}
// 获取总数
string countSql = $"SELECT COUNT(*) FROM {TableName} WHERE {where}";
int totalCount = KcDb.DBInteger(countSql);
// 获取分页数据
string dataSql = $@"
SELECT * FROM {TableName}
WHERE {where}
ORDER BY {orderBy}
LIMIT {pageSize} OFFSET {offset}";
try
{
DataTable dt = KcDb.DtRead(dataSql);
List<T> data = MapDataTableToList(dt);
return new PagedResult<T>
{
Data = data,
TotalCount = totalCount,
PageIndex = pageIndex,
PageSize = pageSize
};
}
catch (Exception ex)
{
LogError($"GetPagedWithCondition failed for {TableName}", ex);
throw;
}
}
// 10. 批量插入
public virtual bool BulkInsert(List<T> entities)
{
if (entities == null || entities.Count == 0)
return false;
try
{
// 使用KcDb的批量更新功能
DataTable dt = MapListToDataTable(entities);
dt.TableName = TableName;
// 使用GetDtSaven进行批量插入
return KcDb.GetDtSaven(TableName, dt);
}
catch (Exception ex)
{
LogError($"BulkInsert failed for {TableName}, Count: {entities.Count}", ex);
throw;
}
}
// 11. 批量更新
public virtual bool BulkUpdate(List<T> entities)
{
if (entities == null || entities.Count == 0)
return false;
try
{
// 为每个实体设置更新时间
foreach (var entity in entities)
{
entity.UpdateTime = DateTime.Now;
}
DataTable dt = MapListToDataTable(entities);
dt.TableName = TableName;
// 使用GetDtSaven进行批量更新
return KcDb.GetDtSaven(TableName, dt);
}
catch (Exception ex)
{
LogError($"BulkUpdate failed for {TableName}, Count: {entities.Count}", ex);
throw;
}
}
// 辅助方法:映射DataRow到实体
protected virtual T MapDataRowToEntity(DataRow row)
{
if (row == null) return default(T);
T entity = new T();
foreach (DataColumn column in row.Table.Columns)
{
try
{
var property = typeof(T).GetProperty(column.ColumnName);
if (property != null && property.CanWrite && row[column] != DBNull.Value)
{
// 类型转换
object value = Convert.ChangeType(row[column], property.PropertyType);
property.SetValue(entity, value, null);
}
}
catch
{
// 忽略转换错误
}
}
return entity;
}
// 辅助方法:映射DataTable到List
protected virtual List<T> MapDataTableToList(DataTable dt)
{
List<T> list = new List<T>();
if (dt != null && dt.Rows.Count > 0)
{
foreach (DataRow row in dt.Rows)
{
T entity = MapDataRowToEntity(row);
if (entity != null)
{
list.Add(entity);
}
}
}
return list;
}
// 辅助方法:映射List到DataTable
protected virtual DataTable MapListToDataTable(List<T> entities)
{
DataTable dt = new DataTable(TableName);
if (entities == null || entities.Count == 0)
return dt;
// 添加列
var properties = typeof(T).GetProperties();
foreach (var prop in properties)
{
dt.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
}
// 添加行
foreach (var entity in entities)
{
DataRow row = dt.NewRow();
foreach (var prop in properties)
{
object value = prop.GetValue(entity, null) ?? DBNull.Value;
row[prop.Name] = value;
}
dt.Rows.Add(row);
}
return dt;
}
// 获取插入属性
protected virtual Dictionary<string, string> GetInsertProperties(T entity)
{
var dict = new Dictionary<string, string>();
var properties = typeof(T).GetProperties();
foreach (var prop in properties)
{
// 跳过主键(如果是自增)
if (prop.Name == PrimaryKey && prop.PropertyType == typeof(int))
continue;
object value = prop.GetValue(entity, null);
dict[prop.Name] = EscapeSqlValue(value);
}
return dict;
}
// 获取更新属性
protected virtual Dictionary<string, string> GetUpdateProperties(T entity)
{
var dict = new Dictionary<string, string>();
var properties = typeof(T).GetProperties();
foreach (var prop in properties)
{
// 跳过主键和创建时间、创建用户
if (prop.Name == PrimaryKey || prop.Name == "CreateTime" || prop.Name == "CreateUser")
continue;
object value = prop.GetValue(entity, null);
dict[prop.Name] = EscapeSqlValue(value);
}
return dict;
}
// SQL值转义
protected virtual string EscapeSqlValue(object value)
{
if (value == null || value == DBNull.Value)
return "NULL";
Type type = value.GetType();
if (type == typeof(string))
{
return $"'{value.ToString().Replace("'", "''")}'";
}
else if (type == typeof(DateTime))
{
return $"'{((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss")}'";
}
else if (type == typeof(bool))
{
return (bool)value ? "true" : "false";
}
else if (type == typeof(int) || type == typeof(long) ||
type == typeof(decimal) || type == typeof(double) ||
type == typeof(float))
{
return value.ToString();
}
else
{
return $"'{value.ToString().Replace("'", "''")}'";
}
}
// 错误日志
protected virtual void LogError(string message, Exception ex)
{
// 可以扩展为使用KcDb的记录错误功能
Console.WriteLine($"{message}: {ex.Message}");
}
}
(三)具体业务 DAO 实现示例
3.1 用户实体和DAO
// 用户实体
public class User : BaseEntity
{
public string UserCode { get; set; } // 用户编码
public string UserName { get; set; } // 用户姓名
public string Password { get; set; } // 密码(加密后)
public string Email { get; set; } // 邮箱
public string Phone { get; set; } // 电话
public int DepartmentId { get; set; } // 部门ID
public int RoleId { get; set; } // 角色ID
public bool IsActive { get; set; } = true; // 是否激活
public DateTime? LastLoginTime { get; set; }// 最后登录时间
public string LastLoginIP { get; set; } // 最后登录IP
// 导航属性(虚拟属性,用于关联查询)
public virtual Department Department { get; set; }
public virtual Role Role { get; set; }
}
// 用户DAO
public class UserDao : BaseDao<User>
{
protected override string TableName => "sys_users";
// 扩展方法:根据用户名获取用户
public User GetByUserCode(string userCode)
{
string sql = $"SELECT * FROM {TableName} WHERE UserCode = '{EscapeSqlValue(userCode)}' AND IsDeleted = false";
try
{
DataTable dt = KcDb.DtRead(sql);
if (dt != null && dt.Rows.Count > 0)
{
return MapDataRowToEntity(dt.Rows[0]);
}
return null;
}
catch (Exception ex)
{
LogError($"GetByUserCode failed for {TableName}, UserCode: {userCode}", ex);
throw;
}
}
// 扩展方法:验证用户登录
public User ValidateLogin(string userCode, string password)
{
string sql = $@"
SELECT * FROM {TableName}
WHERE UserCode = '{EscapeSqlValue(userCode)}'
AND Password = '{EscapeSqlValue(password)}'
AND IsActive = true
AND IsDeleted = false";
try
{
DataTable dt = KcDb.DtRead(sql);
if (dt != null && dt.Rows.Count > 0)
{
var user = MapDataRowToEntity(dt.Rows[0]);
// 更新最后登录信息
UpdateLastLogin(user.Id);
return user;
}
return null;
}
catch (Exception ex)
{
LogError($"ValidateLogin failed for {TableName}, UserCode: {userCode}", ex);
throw;
}
}
// 更新最后登录信息
private void UpdateLastLogin(int userId)
{
string sql = $@"
UPDATE {TableName}
SET LastLoginTime = NOW(),
LastLoginIP = '{GetClientIP()}'
WHERE Id = {userId}";
KcDb.DBexec(sql);
}
// 获取客户端IP(示例方法)
private string GetClientIP()
{
// 实际项目中从HttpContext或其他方式获取
return "127.0.0.1";
}
// 重写插入方法,添加业务逻辑
public override int Insert(User entity)
{
// 检查用户编码是否已存在
if (UserCodeExists(entity.UserCode))
{
throw new InvalidOperationException($"用户编码 {entity.UserCode} 已存在");
}
// 密码加密(示例)
entity.Password = EncryptPassword(entity.Password);
return base.Insert(entity);
}
// 检查用户编码是否存在
private bool UserCodeExists(string userCode)
{
string sql = $"SELECT COUNT(*) FROM {TableName} WHERE UserCode = '{EscapeSqlValue(userCode)}' AND IsDeleted = false";
int count = KcDb.DBInteger(sql);
return count > 0;
}
// 密码加密(示例)
private string EncryptPassword(string password)
{
// 实际项目中应该使用安全的加密方式
// 这里只是示例
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(password));
}
// 获取用户列表(带部门和角色信息)
public DataTable GetUserListWithDetails()
{
string sql = @"
SELECT u.*,
d.DepartmentName,
r.RoleName
FROM sys_users u
LEFT JOIN sys_departments d ON u.DepartmentId = d.Id
LEFT JOIN sys_roles r ON u.RoleId = r.Id
WHERE u.IsDeleted = false
ORDER BY u.CreateTime DESC";
return KcDb.DtRead(sql);
}
}
3.2 产品实体和DAO
// 产品实体
public class Product : BaseEntity
{
public string ProductCode { get; set; } // 产品编码
public string ProductName { get; set; } // 产品名称
public string ProductType { get; set; } // 产品类型
public string Unit { get; set; } // 单位
public decimal Price { get; set; } // 单价
public decimal Cost { get; set; } // 成本
public decimal StockQuantity { get; set; } // 库存数量
public decimal MinStock { get; set; } // 最小库存
public decimal MaxStock { get; set; } // 最大库存
public string Specifications { get; set; } // 规格
public string Manufacturer { get; set; } // 生产厂家
public string Supplier { get; set; } // 供应商
public bool IsActive { get; set; } = true; // 是否启用
public string Remarks { get; set; } // 备注
}
// 产品DAO
public class ProductDao : BaseDao<Product>
{
protected override string TableName => "inv_products";
// 扩展方法:根据产品编码获取产品
public Product GetByProductCode(string productCode)
{
string whereClause = $"ProductCode = '{EscapeSqlValue(productCode)}'";
var products = GetByCondition(whereClause);
return products.FirstOrDefault();
}
// 扩展方法:更新库存
public bool UpdateStock(int productId, decimal quantityChange)
{
string sql = $@"
UPDATE {TableName}
SET StockQuantity = StockQuantity + {quantityChange},
UpdateTime = NOW()
WHERE Id = {productId}";
try
{
KcDb.DBexec(sql);
return true;
}
catch (Exception ex)
{
LogError($"UpdateStock failed for product {productId}, change: {quantityChange}", ex);
throw;
}
}
// 扩展方法:批量更新库存
public bool BatchUpdateStock(Dictionary<int, decimal> stockChanges)
{
if (stockChanges == null || stockChanges.Count == 0)
return true;
try
{
// 使用事务确保一致性
using (var transaction = new System.Transactions.TransactionScope())
{
foreach (var kvp in stockChanges)
{
UpdateStock(kvp.Key, kvp.Value);
}
transaction.Complete();
return true;
}
}
catch (Exception ex)
{
LogError($"BatchUpdateStock failed, count: {stockChanges.Count}", ex);
throw;
}
}
// 扩展方法:获取低库存产品
public List<Product> GetLowStockProducts()
{
string whereClause = "StockQuantity <= MinStock AND IsActive = true";
return GetByCondition(whereClause);
}
// 扩展方法:搜索产品
public List<Product> SearchProducts(string keyword, string productType = null)
{
StringBuilder where = new StringBuilder();
where.Append("IsDeleted = false AND IsActive = true");
if (!string.IsNullOrEmpty(keyword))
{
where.Append($" AND (ProductCode LIKE '%{EscapeSqlValue(keyword)}%' ");
where.Append($"OR ProductName LIKE '%{EscapeSqlValue(keyword)}%' ");
where.Append($"OR Specifications LIKE '%{EscapeSqlValue(keyword)}%')");
}
if (!string.IsNullOrEmpty(productType))
{
where.Append($" AND ProductType = '{EscapeSqlValue(productType)}'");
}
return GetByCondition(where.ToString());
}
// 获取产品销售统计
public DataTable GetProductSalesStats(DateTime startDate, DateTime endDate)
{
string sql = $@"
SELECT
p.Id,
p.ProductCode,
p.ProductName,
p.Unit,
SUM(od.Quantity) as TotalQuantity,
SUM(od.Amount) as TotalAmount,
AVG(od.UnitPrice) as AvgPrice
FROM inv_products p
LEFT JOIN sales_order_details od ON p.Id = od.ProductId
LEFT JOIN sales_orders o ON od.OrderId = o.Id
WHERE o.OrderDate BETWEEN '{startDate:yyyy-MM-dd}' AND '{endDate:yyyy-MM-dd}'
AND o.Status = '已完成'
AND p.IsDeleted = false
GROUP BY p.Id, p.ProductCode, p.ProductName, p.Unit
ORDER BY TotalAmount DESC";
return KcDb.DtRead(sql);
}
}
(四)DAO工厂和服务层
4.1 DAO工厂
// DAO工厂(单例模式)
public class DaoFactory
{
private static DaoFactory _instance;
private static readonly object _lock = new object();
private Dictionary<Type, object> _daoCache;
private DaoFactory()
{
_daoCache = new Dictionary<Type, object>();
}
public static DaoFactory Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new DaoFactory();
}
}
}
return _instance;
}
}
// 获取DAO实例
public T GetDao<T>() where T : class, new()
{
Type daoType = typeof(T);
lock (_daoCache)
{
if (!_daoCache.ContainsKey(daoType))
{
_daoCache[daoType] = new T();
}
return _daoCache[daoType] as T;
}
}
// 注册自定义DAO
public void RegisterDao<T>(T daoInstance) where T : class
{
Type daoType = typeof(T);
lock (_daoCache)
{
_daoCache[daoType] = daoInstance;
}
}
}
4.2 服务层示例
// 用户服务
public class UserService
{
private readonly UserDao _userDao;
public UserService()
{
_userDao = DaoFactory.Instance.GetDao<UserDao>();
}
// 用户登录
public User Login(string userCode, string password)
{
// 参数验证
if (string.IsNullOrEmpty(userCode) || string.IsNullOrEmpty(password))
{
throw new ArgumentException("用户名和密码不能为空");
}
// 调用DAO验证登录
User user = _userDao.ValidateLogin(userCode, password);
if (user == null)
{
throw new InvalidOperationException("用户名或密码错误");
}
// 记录登录日志
LogLogin(user.Id, true, "登录成功");
return user;
}
// 创建用户
public int CreateUser(User user, int createdByUserId)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
// 业务验证
ValidateUser(user);
// 设置创建人
user.CreateUser = KcDb.JLyh;
// 调用DAO插入
int userId = _userDao.Insert(user);
// 记录操作日志
LogUserOperation(createdByUserId, "CREATE", $"创建用户: {user.UserCode}");
return userId;
}
// 更新用户
public bool UpdateUser(User user, int updatedByUserId)
{
if (user == null || user.Id <= 0)
throw new ArgumentException("无效的用户信息");
// 业务验证
ValidateUser(user);
// 调用DAO更新
bool result = _userDao.Update(user);
if (result)
{
// 记录操作日志
LogUserOperation(updatedByUserId, "UPDATE", $"更新用户: {user.UserCode}");
}
return result;
}
// 删除用户(软删除)
public bool DeleteUser(int userId, int deletedByUserId, string reason = "")
{
if (userId <= 0)
return false;
// 获取用户信息用于日志
User user = _userDao.GetById(userId);
if (user == null)
return false;
// 不能删除自己
if (userId == deletedByUserId)
{
throw new InvalidOperationException("不能删除当前登录用户");
}
// 调用DAO删除
bool result = _userDao.Delete(userId, true);
if (result)
{
// 记录操作日志
LogUserOperation(deletedByUserId, "DELETE", $"删除用户: {user.UserCode}, 原因: {reason}");
}
return result;
}
// 获取用户分页列表
public PagedResult<User> GetUserPagedList(int pageIndex, int pageSize,
string department = null, string role = null, string keyword = null)
{
StringBuilder whereClause = new StringBuilder();
List<object> parameters = new List<object>();
if (!string.IsNullOrEmpty(department))
{
whereClause.Append("DepartmentId IN (SELECT Id FROM sys_departments WHERE DepartmentName LIKE {0})");
parameters.Add($"%{department}%");
}
if (!string.IsNullOrEmpty(role))
{
if (whereClause.Length > 0) whereClause.Append(" AND ");
whereClause.Append("RoleId IN (SELECT Id FROM sys_roles WHERE RoleName LIKE {1})");
parameters.Add($"%{role}%");
}
if (!string.IsNullOrEmpty(keyword))
{
if (whereClause.Length > 0) whereClause.Append(" AND ");
whereClause.Append("(UserCode LIKE {2} OR UserName LIKE {2} OR Email LIKE {2})");
parameters.Add($"%{keyword}%");
}
return _userDao.GetPagedWithCondition(pageIndex, pageSize,
whereClause.ToString(), "CreateTime DESC", parameters.ToArray());
}
// 用户验证逻辑
private void ValidateUser(User user)
{
if (string.IsNullOrEmpty(user.UserCode))
throw new ArgumentException("用户编码不能为空");
if (string.IsNullOrEmpty(user.UserName))
throw new ArgumentException("用户姓名不能为空");
if (string.IsNullOrEmpty(user.Email) || !IsValidEmail(user.Email))
throw new ArgumentException("邮箱格式不正确");
// 更多验证规则…
}
// 邮箱验证
private bool IsValidEmail(string email)
{
try
{
var addr = new System.Net.Mail.MailAddress(email);
return addr.Address == email;
}
catch
{
return false;
}
}
// 记录登录日志
private void LogLogin(int userId, bool success, string message)
{
string sql = $@"
INSERT INTO sys_login_logs (UserId, LoginTime, Success, Message, IPAddress)
VALUES ({userId}, NOW(), {success}, '{message.Replace("'", "''")}', '{GetClientIP()}')";
KcDb.DBexec(sql);
}
// 记录用户操作日志
private void LogUserOperation(int userId, string operation, string description)
{
string sql = $@"
INSERT INTO sys_operation_logs (UserId, OperationTime, OperationType, Description, IPAddress)
VALUES ({userId}, NOW(), '{operation}', '{description.Replace("'", "''")}', '{GetClientIP()}')";
KcDb.DBexec(sql);
}
// 获取客户端IP
private string GetClientIP()
{
// 实际项目中从HttpContext或其他方式获取
return "127.0.0.1";
}
}
(五)使用示例
5.1 基本使用
// 初始化数据库连接
KcDb.SvNAME = "localhost";
KcDb.DBname = "erp_db";
KcDb.DLmc = "postgres";
KcDb.DLmm = "password";
KcDb.DBLJ(); // 建立连接
// 使用DAO工厂获取实例
var userDao = DaoFactory.Instance.GetDao<UserDao>();
var productDao = DaoFactory.Instance.GetDao<ProductDao>();
// 或者直接实例化
var userDao2 = new UserDao();
var productDao2 = new ProductDao();
// 使用服务层
var userService = new UserService();
5.2 CRUD操作示例
// 1. 创建用户
var newUser = new User
{
UserCode = "zhangsan",
UserName = "张三",
Password = "123456",
Email = "zhangsan@example.com",
DepartmentId = 1,
RoleId = 1
};
int userId = userDao.Insert(newUser);
Console.WriteLine($"创建用户成功,ID: {userId}");
// 2. 查询用户
User user = userDao.GetById(userId);
Console.WriteLine($"查询用户: {user.UserName}");
// 3. 条件查询
List<User> activeUsers = userDao.GetByCondition("IsActive = true AND DepartmentId = 1");
// 4. 分页查询
var pagedResult = userDao.GetPaged(1, 10, "CreateTime DESC");
Console.WriteLine($"总记录数: {pagedResult.TotalCount}, 总页数: {pagedResult.TotalPages}");
// 5. 更新用户
user.Email = "newemail@example.com";
userDao.Update(user);
// 6. 删除用户(软删除)
userDao.Delete(userId, true);
5.3 事务处理示例
// 使用事务处理复杂操作
public bool CreateOrderWithTransaction(Order order, List<OrderDetail> details)
{
try
{
// 开启事务
// 注意:KcDb本身没有显式事务支持,可以使用TransactionScope
using (var transaction = new System.Transactions.TransactionScope())
{
// 1. 插入订单
OrderDao orderDao = new OrderDao();
int orderId = orderDao.Insert(order);
全文总结
本文档系统性地介绍了ERP系统中数据访问层的技术实现方案。开篇详细讲解了在.NET Framework 4.7.2环境下使用Npgsql驱动连接PostgreSQL的完整配置流程,包括NuGet包安装、连接字符串配置和基础数据库操作类设计。文档的核心是KcDb.cs静态类的深度解析,该封装类提供了数据库连接管理、SQL执行(DBString、DBInteger、DtRead等)、DataTable双向同步(DtSaves、DtSaven)、系统参数缓存管理以及完善的错误日志记录机制,特别实现了SQL语句自动生成以支持增删改查操作。
文档进一步阐述了基于DAO.NET模式的分层架构设计,定义了IBaseDao泛型接口和BaseDao抽象基类,实现了实体映射、分页查询、批量操作和软删除等现代数据访问特性。通过用户DAO、产品DAO等具体业务实现示例,展示了如何在KcDb基础上构建 Repository 层,以及如何通过DAO工厂和服务层实现业务逻辑与数据访问的解耦。整体方案既保留了传统ADO.NET的灵活性,又引入了现代ORM的分层思想,为WinForms环境下的ERP系统提供了高效、可维护的数据访问解决方案。
网硕互联帮助中心

评论前必须登录!
注册