如何优雅封装.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失控:

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


相关推荐
geBR OTTE2 小时前
Spring Boot中集成MyBatis操作数据库详细教程
数据库·spring boot·mybatis
2401_840192272 小时前
数据库连接池和java servlet
java·数据库·servlet
honortech2 小时前
docker 配置 MySQL 主从数据库
数据库·mysql·docker
爬山算法2 小时前
MongoDB(75)如何配置TLS/SSL加密?
数据库·mongodb·ssl
无风听海2 小时前
.NET10之Web API Action参数来源自动推断
.net
庞轩px2 小时前
JWT + Redis 双 Token 机制:从原理到实战
数据库·redis·缓存·jwt·token·登录认证
splage2 小时前
maven导入spring框架
数据库·spring·maven
HalvmånEver2 小时前
MySQL数据库基础入门总结(从0到1)
linux·数据库·mysql