如何优雅封装.NET数据库访问层(彻底告别拼接SQL)


【进阶篇】如何优雅封装.NET数据库访问层(彻底告别拼接SQL)

在上一篇中,我们已经分析了:

👉 为什么不建议使用SQL拼接,而应该使用参数化查询

但很多开发者在实际项目中仍然会遇到一个问题:

👉 参数化写法太繁琐,代码臃肿,开发效率低

那么有没有一种方式可以做到:

  • 写法简洁(接近拼接SQL)
  • 自动参数化(安全)
  • 统一规范(易维护)
  • 支持扩展(日志、事务、重试)

答案是:封装数据库访问层


一、为什么必须封装?

先说结论:

👉 不封装数据库访问层,项目后期一定会乱

常见问题:

1. SQL散落各处

csharp 复制代码
// A类
var sql = "SELECT * FROM A WHERE id=@id";

// B类
var sql = "SELECT * FROM A WHERE id=" + id;

👉 写法不统一,维护困难


2. 参数化风格混乱

csharp 复制代码
cmd.Parameters.AddWithValue("@id", id);

👉 每个人写法都不一样


3. 无法统一扩展

比如你想加:

  • SQL日志
  • 执行耗时
  • 重试机制
  • 事务控制

👉 需要全项目改代码 ❌


👉 所以核心目标:

把所有数据库操作收口到一层


二、设计目标(重点)

我们要实现一个工具层,满足:

目标 说明
简洁 类似拼接SQL写法
安全 自动参数化
通用 支持查询/增删改
可扩展 日志/事务/重试
易维护 统一入口

三、推荐方案:类Dapper风格封装

我们实现一个方法:

csharp 复制代码
Execute(sql, new { id = 1, name = "张三" });

👉 看起来像拼接,但其实是参数化


四、核心实现(通用工具类)

1. 执行SQL(增删改)

csharp 复制代码
public static int Execute(string sql, object param = null)
{
    using (var conn = new SqlConnection(connStr))
    {
        using (var cmd = new SqlCommand(sql, conn))
        {
            if (param != null)
            {
                AddParameters(cmd, param);
            }

            conn.Open();
            return cmd.ExecuteNonQuery();
        }
    }
}

2. 查询单条

csharp 复制代码
public static T QueryFirst<T>(string sql, object param = null) where T : new()
{
    using (var conn = new SqlConnection(connStr))
    using (var cmd = new SqlCommand(sql, conn))
    {
        if (param != null)
        {
            AddParameters(cmd, param);
        }

        conn.Open();
        using (var reader = cmd.ExecuteReader())
        {
            if (!reader.Read()) return default;

            var obj = new T();
            foreach (var prop in typeof(T).GetProperties())
            {
                if (!reader.HasColumn(prop.Name)) continue;

                var val = reader[prop.Name];
                if (val != DBNull.Value)
                    prop.SetValue(obj, val);
            }

            return obj;
        }
    }
}

3. 参数自动映射(核心)

csharp 复制代码
private static void AddParameters(SqlCommand cmd, object param)
{
    foreach (var prop in param.GetType().GetProperties())
    {
        var value = prop.GetValue(param) ?? DBNull.Value;
        cmd.Parameters.AddWithValue("@" + prop.Name, value);
    }
}

👉 这一步就是关键:

把匿名对象 → 自动转成SQL参数


五、使用方式(对比)

旧写法(拼接 ❌)

csharp 复制代码
var sql = $"SELECT * FROM User WHERE Name='{name}'";

新写法(推荐 ✅)

csharp 复制代码
var sql = "SELECT * FROM User WHERE Name=@Name";
var user = QueryFirst<User>(sql, new { Name = name });

👉 优点:

  • 简洁
  • 安全
  • 可读性强

六、进阶优化(项目实战重点)

1. SQL日志(强烈建议)

csharp 复制代码
Stopwatch sw = Stopwatch.StartNew();

var result = cmd.ExecuteNonQuery();

sw.Stop();
Console.WriteLine($"SQL执行耗时:{sw.ElapsedMilliseconds}ms");
Console.WriteLine(sql);

👉 在MES/WMS中非常重要(排查慢SQL)


2. 异常重试机制

适用于:

  • 数据库瞬时连接失败
  • 网络抖动
csharp 复制代码
int retry = 3;
while (retry-- > 0)
{
    try
    {
        return cmd.ExecuteNonQuery();
    }
    catch
    {
        if (retry == 0) throw;
        Thread.Sleep(200);
    }
}

3. 事务封装

csharp 复制代码
public static void ExecuteInTransaction(Action<SqlConnection, SqlTransaction> action)
{
    using var conn = new SqlConnection(connStr);
    conn.Open();

    using var tran = conn.BeginTransaction();

    try
    {
        action(conn, tran);
        tran.Commit();
    }
    catch
    {
        tran.Rollback();
        throw;
    }
}

👉 用法:

csharp 复制代码
ExecuteInTransaction((conn, tran) =>
{
    Execute("INSERT ...", param1, conn, tran);
    Execute("UPDATE ...", param2, conn, tran);
});

4. 防止动态SQL注入(白名单)

csharp 复制代码
var allowedColumns = new[] { "Name", "Age" };

if (!allowedColumns.Contains(column))
{
    throw new Exception("非法字段");
}

七、适配你当前技术栈(重点)

你现在用:

  • .NET
  • PostgreSQL / SQL Server
  • MES / WMS
  • 高并发 + 设备数据

👉 建议:

1. PostgreSQL(Npgsql)

把:

csharp 复制代码
SqlCommand

换成:

csharp 复制代码
NpgsqlCommand

2. 高频接口建议

👉 必须加:

  • SQL日志
  • 超时控制
  • 重试机制

3. 设备数据(你做过Socket)

👉 强烈建议:

  • 所有入库操作必须参数化
  • 禁止拼接

八、什么时候不用自己封装?

直接说实话:

👉 如果项目不复杂,直接用:

  • Dapper(强烈推荐)
  • SqlSugar(你已经在用)
  • FreeSQL

👉 你自己封装适合:

  • 公司没有规范
  • 项目需要统一风格
  • 想做技术沉淀

九、总结

👉 数据库访问层的本质:

不是为了封装而封装,而是为了统一和可控


最后一句(给你点"实战味"的结尾)

在小项目里:

👉 拼接SQL你可能感觉不到问题

但在生产环境中:

  • MES系统
  • 多设备接入
  • 多系统交互

👉 一旦SQL失控:

不是报错的问题,是直接影响生产。


相关推荐
2301_813599551 小时前
Go语言怎么做秒杀系统_Go语言秒杀系统实战教程【实用】
jvm·数据库·python
NCIN EXPE6 小时前
redis 使用
数据库·redis·缓存
MongoDB 数据平台6 小时前
为编码代理引入 MongoDB 代理技能和插件
数据库·mongodb
极客on之路6 小时前
mysql explain type 各个字段解释
数据库·mysql
代码雕刻家6 小时前
MySQL与SQL Server的基本指令
数据库·mysql·sqlserver
lThE ANDE6 小时前
开启mysql的binlog日志
数据库·mysql
yejqvow126 小时前
CSS如何控制placeholder文字的颜色_使用--placeholder伪元素
jvm·数据库·python
oLLI PILO6 小时前
nacos2.3.0 接入pgsql或其他数据库
数据库
m0_743623926 小时前
HTML怎么创建多语言切换器_HTML语言选择下拉结构【指南】
jvm·数据库·python