云计算百科
云计算领域专业知识百科平台

编程与数学 03-008 《看潮企业管理软件》项目开发 07 数据访问 3-3

编程与数学 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 执行类方法
    方法名返回类型功能说明SQL类型
    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 数据类型映射表
    .NET类型PostgreSQL类型处理方式
    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 相关方法
    方法名Get/Set参数类型缓存策略
    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 潜在风险

  • SQL注入:字符串拼接SQL语句
  • 连接泄露:未正确关闭连接
  • 敏感信息:密码明文传输
  • 错误信息:详细错误暴露给用户
  • 6.2 安全建议

  • 参数化查询:所有动态SQL使用参数
  • 输入验证:对用户输入进行严格验证
  • 最小权限:数据库用户使用最小必要权限
  • 错误处理:生产环境隐藏详细错误
  • 连接加密:使用SSL连接数据库
  • 6.3 审计日志

    • 所有SQL操作可通过x9_jlsql表审计
    • 错误信息记录在x9_errcode表
    • 支持操作追踪和故障排查

    (七)扩展与维护

    7.1 扩展建议

  • 异步支持:添加异步方法(async/await)
  • ORM集成:考虑集成Dapper或Entity Framework
  • 配置外部化:连接字符串移到配置文件中
  • 依赖注入:改造为可注入服务
  • 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 调试技巧

  • 启用SQL日志:设置pSFSJ = true
  • 查看错误表:SELECT * FROM x9_errcode
  • 使用工具:pgAdmin、Npgsql日志
  • 分步调试:复杂操作分步执行验证
  • (九)总结

    9.1 优点

  • 功能全面:覆盖大部分数据库操作需求
  • 使用简单:提供统一的静态方法调用
  • 错误处理完善:完整的错误记录和提示
  • 参数管理:内置参数缓存机制
  • 日志完整:支持操作审计和故障排查
  • 9.2 待改进

  • 安全性:需要加强SQL注入防护
  • 性能:大数据量操作需要优化
  • 架构:可考虑服务化和依赖注入
  • 异步:缺乏异步操作支持
  • 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系统提供了高效、可维护的数据访问解决方案。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 编程与数学 03-008 《看潮企业管理软件》项目开发 07 数据访问 3-3
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!