今天带来ASP.NET 企业级架构核心篇——MVC 扩展分层架构 + DAL 层 (Repository+EFCore)实战,纯干货、无废话,带代码、避坑、流程图,新手也能直接落地!
本篇主打:规范命名、低耦合、高可维护、易扩展,是中小厂到大厂通用的最佳实践!

在这里插入图片描述

一、先搞懂:什么是 MVC 扩展分层架构?(通俗类比)

小节:架构分层 = 公司部门分工
很多新手一上来就把代码写在 Controller 里,后期改不动、查错难,本质是没有分工
我们把项目拆成5 层标准结构(MVC 基础上扩展),就像一家正规公司:
表格

层名称 英文缩写 职责 生活类比
表现层 UI/Controller 接收请求、返回结果 前台客服
业务逻辑层 BLL 处理核心业务规则 运营经理
数据访问层 DAL 只和数据库打交道 仓库管理员
模型层 Model 数据实体、DTO 商品档案
核心公共层 Core 工具类、常量、枚举 公司行政部

标准分层流程图

返回结果

Controller 表现层

Service 业务逻辑层

Repository 数据访问层

EFCore

数据库

小节:为什么必须分层?

  1. 解耦 : 改数据库不影响业务,改业务不影响页面
  2. 可维护: 代码各司其职,一眼找到问题
  3. 可测试: 业务逻辑可单独单元测试
  4. 规范: 多人协作不混乱

二、DAL 层核心:Repository 仓储模式 + EFCore

小节:仓储模式 = 数据库专属 “管家”
仓储模式(Repository)是DAL 层唯一和数据库交互的入口,作用:

  • 统一 CRUD,避免重复代码
  • 屏蔽 EFCore 细节,上层不用关心数据库
    方便切换 ORM(以后不用 EFCore 也不影响业务)
    一句话: Service 只管调用,Repository 只管干活,EFCore 只管执行 SQL。

三、实战代码:从 0 到 1 搭建标准架构

环境准备

  • .NET 6/7/8(LTS 版本最佳)
  • ASP.NET MVC
  • EFCore + SQL Server
  • 依赖注入(微软原生 DI)

1. 第一步:定义实体(Model 层)

小节:实体 = 数据库表的 “代码映射”
规范:

  • 实体放在Models/Entities文件夹
  • 类名 = 表名(大驼峰)
  • 属性 = 字段(帕斯卡命名)
  • 无业务逻辑,纯数据载体
// Models/Entities/User.cs
using System.ComponentModel.DataAnnotations;

/// <summary>
/// 用户实体(对应数据库User表)
/// </summary>
public class User
{
    [Key] // 主键
    public int Id { get; set; }

    [Required] // 非空
    [MaxLength(20)]
    public string UserName { get; set; }

    [MaxLength(50)]
    public string Email { get; set; }

    public bool IsActive { get; set; } = true; // 默认值
}

2. 第二步:搭建 DbContext(EFCore 核心)

小节:DbContext = 数据库连接 “总开关”
规范:

  • 放在DAL/DbContexts
  • 继承DbContext
  • 构造函数注入配置
  • 用DbSet映射表
// DAL/DbContexts/AppDbContext.cs
using Microsoft.EntityFrameworkCore;
using Models.Entities;

/// <summary>
/// EFCore 数据库上下文
/// </summary>
public class AppDbContext : DbContext
{
    // 注入配置
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    // 映射数据库表
    public DbSet<User> Users => Set<User>();

    // 统一配置种子数据/约束
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

3. 第三步:定义仓储接口(抽象规范)

小节:接口 = “管家的工作清单”
最佳实践: 先写接口,后写实现,面向接口编程!

// DAL/Repositories/IRepository/IRepository.cs
using System.Linq.Expressions;

/// <summary>
/// 通用仓储接口(所有实体通用CRUD)
/// </summary>
/// <typeparam name="T">实体类型</typeparam>
public interface IRepository<T> where T : class
{
    // 查询
    Task<T> GetByIdAsync(int id);
    Task<List<T>> GetAllAsync();
    Task<List<T>> GetListAsync(Expression<Func<T, bool>> where);

    // 新增
    Task AddAsync(T entity);
    Task AddRangeAsync(List<T> entities);

    // 修改
    void Update(T entity);
    void UpdateRange(List<T> entities);

    // 删除
    void Delete(T entity);
    void DeleteRange(List<T> entities);

    // 提交事务
    Task<int> SaveChangesAsync();
}
4. 第四步:实现通用仓储(核心代码)
小节:实现类 = “管家真正干活”
csharp
运行
// DAL/Repositories/Impl/RepositoryBase.cs
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

/// <summary>
/// 仓储基类(所有实体共用实现)
/// </summary>
public class RepositoryBase<T> : IRepository<T> where T : class
{
    protected readonly AppDbContext _dbContext;
    protected readonly DbSet<T> _dbSet;

    // 依赖注入DbContext
    public RepositoryBase(AppDbContext dbContext)
    {
        _dbContext = dbContext;
        _dbSet = dbContext.Set<T>();
    }

    #region 查询
    public async Task<T> GetByIdAsync(int id)
    {
        return await _dbSet.FindAsync(id);
    }

    public async Task<List<T>> GetAllAsync()
    {
        return await _dbSet.AsNoTracking().ToListAsync();
    }

    public async Task<List<T>> GetListAsync(Expression<Func<T, bool>> where)
    {
        return await _dbSet.AsNoTracking().Where(where).ToListAsync();
    }
    #endregion

    #region 增删改
    public async Task AddAsync(T entity)
    {
        await _dbSet.AddAsync(entity);
    }

    public void Update(T entity)
    {
        _dbSet.Update(entity);
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity);
    }
    #endregion

    // 统一提交事务
    public async Task<int> SaveChangesAsync()
    {
        return await _dbContext.SaveChangesAsync();
    }
}

5. 第五步:注册服务(Program.cs)

小节:依赖注入 = “给系统分配员工”

// Program.cs
using DAL.DbContexts;
using DAL.Repositories.Impl;
using DAL.Repositories.IRepository;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// 1. 注册EFCore
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// 2. 注册仓储(生命周期:Scoped 一次请求一个实例)
builder.Services.AddScoped(typeof(IRepository<>), typeof(RepositoryBase<>));

// 3. MVC控制器
builder.Services.AddControllersWithViews();

var app = builder.Build();

// 中间件省略...
app.Run();

6. 第六步:Controller 调用演示

小节:上层调用 = “客服找经理,经理找仓库”

// Controllers/UserController.cs
using DAL.Repositories.IRepository;
using Microsoft.AspNetCore.Mvc;
using Models.Entities;

public class UserController : Controller
{
    // 直接注入仓储
    private readonly IRepository<User> _userRepository;

    public UserController(IRepository<User> userRepository)
    {
        _userRepository = userRepository;
    }

    // 查询用户列表
    public async Task<IActionResult> Index()
    {
        var users = await _userRepository.GetAllAsync();
        return View(users);
    }

    // 新增用户
    [HttpPost]
    public async Task<IActionResult> Add(User user)
    {
        await _userRepository.AddAsync(user);
        await _userRepository.SaveChangesAsync();
        return RedirectToAction("Index");
    }
}

四、开发必看:Repository+EFCore 10 大经典坑(90% 人中招)

小节:踩坑 = 新手 “必经之路”,避开就是高手

1.忘记调用 SaveChangesAsync

  • 坑:增删改执行了,但数据库没变化
  • 原因:EFCore 是状态跟踪,必须提交才生效
  • 类比:你把商品放进仓库,但没 “确认入库”

2.滥用 AsNoTracking

  • 坑:查询快,但修改时报错
  • 规则:只读用 AsNoTracking,要修改必须不用

3.在仓储外操作 DbContext

  • 坑:架构失效,代码到处改数据库
  • 规范:所有数据库操作必须走 Repository

4.同步方法代替异步

  • 坑:高并发卡死,性能暴跌
  • 规范:EFCore 全部用Async结尾方法

5.事务不统一

  • 坑:部分成功部分失败,数据错乱
  • 最佳实践:一个请求一个 SaveChanges

6.主外键懒加载死循环

  • 坑:JSON 序列化爆栈
  • 解决:关闭懒加载 or 使用 DTO

7.实体加业务逻辑

  • 坑:实体不纯粹,难以维护
  • 规范:实体只存数据,业务放 Service

8.泛型仓储写死条件

  • 坑:无法复用,失去仓储意义
  • 解决:用表达式树传条件

9.多个仓储多次 SaveChanges

  • 坑:事务失效,数据不一致
  • 解决:Service 层统一提交

10.连接字符串硬编码

  • 坑:换环境要改代码,不安全
  • 规范:配置文件读取

五、最佳实践总结(直接复制当团队规范)

小节:这一段直接收藏,团队开发统一标准
✅ 命名规范

  • 接口:IXXXRepository
  • 实现:XXXRepository
  • 实体:表名大驼峰
  • 上下文:XXXDbContext

✅ 结构规范

项目/
├─ Models/Entities       实体
├─ DAL/
│  ├─ DbContexts         EFCore上下文
│  ├─ Repositories/
│     ├─ IRepository     仓储接口
│     └─ Impl            仓储实现
├─ BLL/Services          业务层
└─ Controllers           表现层

✅ 职责规范

  • Controller:接收请求、返回视图
  • Service:业务规则、校验、组合仓储
  • Repository:只做 CRUD,无业务逻辑
  • EFCore:只做数据库交互

六、结尾互动

今天把MVC 扩展分层 + DAL 仓储 + EFCore全套代码、架构图、避坑指南一次性讲完了,新手也能直接落地!

留言区聊聊

1.你在项目中踩过 EFCore / 仓储模式的什么坑?
2.下期想看:Service 层业务规范 还是 UnitOfWork 工作单元?
3.代码看不懂的地方,直接截图提问!
我会一一回复,帮你打通架构任督二脉~

总结

1.分层架构 = 分工合作,Controller/Service/Repository 各司其职
2.Repository+EFCore是.NET 企业级标准数据访问方案
3.避开 10 大经典坑,代码可维护性直接提升 10 倍
4.面向接口 + 依赖注入,让项目易扩展、易测试
觉得干货有用,点赞 + 收藏 + 关注,下期更精彩!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐