浅析如何优雅封装.NET数据库访问层

 更新时间:2026年04月02日 15:09:56   作者:孟章豪  
这篇文章主要为大家详细介绍了如何优雅封装.NET数据库访问层,解决SQL拼接和参数化查询的痛点,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

但很多开发者在实际项目中仍然会遇到一个问题: 参数化写法太繁琐,代码臃肿,开发效率低

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

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

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

一、为什么必须封装

先说结论:

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

常见问题:

1. SQL散落各处

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

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

写法不统一,维护困难

2. 参数化风格混乱

cmd.Parameters.AddWithValue("@id", id);

每个人写法都不一样

3. 无法统一扩展

比如你想加:

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

需要全项目改代码 

所以核心目标:把所有数据库操作收口到一层

二、设计目标(重点)

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

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

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

我们实现一个方法:

Execute(sql, new { id = 1, name = "张三" });

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

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

1. 执行SQL(增删改)

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. 查询单条

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. 参数自动映射(核心)

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参数

五、使用方式(对比)

旧写法(拼接)

var sql = $"SELECT * FROM User WHERE Name='{name}'";

新写法(推荐)

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

优点:

  • 简洁
  • 安全
  • 可读性强

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

1. SQL日志(强烈建议)

Stopwatch sw = Stopwatch.StartNew();

var result = cmd.ExecuteNonQuery();

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

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

2. 异常重试机制

适用于:

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

3. 事务封装

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;
    }
}

用法:

ExecuteInTransaction((conn, tran) =>
{
    Execute("INSERT ...", param1, conn, tran);
    Execute("UPDATE ...", param2, conn, tran);
});

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

var allowedColumns = new[] { "Name", "Age" };

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

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

你现在用:

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

建议:

1. PostgreSQL(Npgsql)

把:

SqlCommand

换成:

NpgsqlCommand

2. 高频接口建议

必须加:

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

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

强烈建议:

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

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

直接说实话:

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

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

你自己封装适合:

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

九、总结

数据库访问层的本质:不是为了封装而封装,而是为了统一和可控

到此这篇关于浅析如何优雅封装.NET数据库访问层的文章就介绍到这了,更多相关.NET封装数据库访问层内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论