怎么一步步弄好自己的数据访问层,别太复杂也别乱七八糟
- 问答
- 2026-01-03 19:24:26
- 18
要弄好自己的数据访问层,核心思想就是把它当作一个独立的“服务员”,你的业务逻辑是“顾客”,顾客只管点菜(我需要什么数据),服务员负责去后厨(数据库)拿菜,并且处理好端上来,顾客不需要知道后厨是怎么做菜的,下面一步步来。
第一步:想清楚你要做什么菜(明确需求)
别一上来就写代码,先花点时间想清楚你的应用主要需要处理哪些数据,你做一个简单的博客系统,核心就是“文章”和“用户”,把每个数据对象(实体)的关键属性列出来。
- 文章:ID、标题、内容、作者ID、创建时间。
- 用户:ID、用户名、邮箱。
这一步不用搞得太复杂,脑子里有个清晰的列表就行,目的是让你知道你的“服务员”主要需要端哪几道菜。
第二步:设计你的“菜单”(定义接口)
接口就是你的业务逻辑(顾客)对数据访问层(服务员)下的订单,它只声明“要做什么”,完全不关心“怎么做”,这一步是让数据访问层不乱的关键。
根据第一步的需求,为每个实体定义一个接口,对于“文章”,你的接口里可以声明这些方法:
GetById(int id):根据ID找一篇文章。GetAll():获取所有文章列表。Add(Article article):添加一篇新文章。Update(Article article):更新一篇文章。Delete(int id):删除一篇文章。
注意,这里返回和传入的都是“文章”这个对象,而不是数据库的细节,你的业务代码只跟这些接口打交道,将来哪怕你把数据库从MySQL换成PostgreSQL,或者换成文件存储,只要这个接口不变,你的业务代码就完全不用改。
第三步:雇一个具体的“服务员”(实现接口)
现在你需要真正写代码去操作数据库了,这就是接口的具体实现,你可以选择一个你喜欢的方式,比如直接用SqlConnection和SqlCommand(如果数据库是SQL Server),或者用Dapper这种轻量级的工具。

举个例子,用Dapper实现GetById方法可能长这样(这里只是示意,别纠结语法细节):
public class ArticleRepository : IArticleRepository // 实现刚才定义的接口
{
private readonly string _connectionString;
public ArticleRepository(string connectionString)
{
_connectionString = connectionString;
}
public Article GetById(int id)
{
using var db = new SqlConnection(_connectionString);
var sql = "SELECT * FROM Articles WHERE Id = @Id";
return db.QueryFirstOrDefault<Article>(sql, new { Id = id });
}
// ... 实现其他接口方法
}
关键点在于:所有关于数据库的代码,比如SQL语句、连接字符串,都只应该存在于这一层。 绝对不要泄漏到处理业务逻辑的代码里去。
第四步:建立一个“服务台”(依赖注入)
现在你有接口(菜单)和实现(服务员)了,怎么让顾客(业务逻辑)方便地找到服务员呢?最佳实践是用“依赖注入”容器,这听起来专业,但你可以把它理解成一个服务台。
你提前去服务台登记:“当有人要IArticleRepository时,你就把ArticleRepository这个具体的实例给他”,在你的程序启动时,会做类似这样的配置(以ASP.NET Core为例):
services.AddScoped<IArticleRepository, ArticleRepository>();
这样,在你的控制器或者业务类里,你只需要在构造函数里声明你需要IArticleRepository,框架会自动把实现好的ArticleRepository实例传给你,你不需要自己用new关键字去创建,这样做的好处是,所有依赖关系一目了然,并且测试的时候可以轻松替换成假的实现。

第五步:处理复杂一点的订单(处理关联查询)
有时候顾客点的菜比较复杂,给我这篇文章,连同它的作者信息一起拿来”,如果你的Article对象里只有一个AuthorId,那业务逻辑还得再根据这个ID去调用用户接口,这样效率低,代码也啰嗦。
这时你有几个选择:
- 在接口里增加专门的方法:比如在
IArticleRepository里加一个GetArticleWithAuthor(int articleId),这种方法简单直接,适合常用的复杂查询。 - 使用一种叫“规约模式(Specification Pattern)”的思路:这个稍微高级一点,但思想是定义一个规则对象来描述你想查询什么,然后让数据访问层去解释这个规则并执行,这能让你更灵活地组合查询条件,而不用写无数个专用方法,一开始可以不用这个,等简单方法不够用了再考虑。
第六步:统一处理常见杂事(交叉关注点)
像数据库连接的打开关闭、事务管理这些事,每个方法都要做,很繁琐,你可以在架构上找个地方统一处理。
- 连接管理:像Dapper这样的工具,你只需要打开连接,它执行完SQL后,你在
using语句里关闭就行,如上一步的示例代码。 - 事务:如果多个操作需要作为一个整体(比如转账,一个账户减钱,另一个账户加钱),可以在业务逻辑层开启一个事务,然后在这个事务范围内调用多个数据访问方法,很多ORM框架和依赖注入容器能帮你很好地管理事务作用域。
总结一下核心要点:
- 接口隔离:用接口把“做什么”和“怎么做”分开,这是最重要的一条。
- 单一职责:数据访问层就只负责数据存取,别让它干验证逻辑、发邮件之类的闲事。
- 依赖注入:用服务台来管理依赖,让代码更灵活、更易测试。
- 从简开始:先用最简单的方式实现核心功能,别一开始就追求设计一个能应对所有变化的完美架构,等业务复杂了,再逐步重构。
遵循这些步骤,你的数据访问层就不会乱七八糟,它会有清晰的边界,易于维护和扩展,即使未来要换数据库,代价也会小很多。
本文由革姣丽于2026-01-03发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/73879.html
