【.NET7 WinForm 实战】三层架构+EF Core+多数据库+完整功能(源码+教程+脚本)

【.NET7 WinForm 实战】三层架构+EF Core+多数据库+完整功能(源码+教程+脚本)

今天给大家带来一篇全网最完整的 .NET7 WinForm 三层架构实战教程,涵盖「EF Core+SQL Server/MySQL+Autofac+全局异常+增删改查+搜索+分页+Excel导出」,所有代码可直接复制运行,配套源码、数据库脚本、界面布局图,小白也能轻松上手,可直接作为课设/毕设/实习项目。


一、项目核心亮点

标准三层架构 :UI层(窗体)+ BLL层(业务)+ DAL层(数据),解耦易维护

双数据库支持 :EF Core 适配 SQL Server/MySQL,一键切换

完整功能闭环 :新增/编辑/删除/查询/模糊搜索/分页/Excel导出

企业级特性 :Autofac依赖注入+全局异常处理+日志+界面自适应

零基础友好:逐行注释+图文教程+配套源码,复制即用


二、技术栈清单

技术/框架 版本 用途
.NET 7.0 WinForm 运行框架
EF Core 7.0 数据库ORM框架
SQL Server 2019+ 关系型数据库(主版本)
MySQL 8.0+ 关系型数据库(备选)
Autofac 7.1.0 依赖注入容器
NPOI 2.6.0 Excel导出
Serilog 3.1.1 日志记录

三、项目完整结构

复制代码
WinFormThreeLayerDemo/
├─ Core/                // 核心层:实体/枚举/公共模型
│  ├─ Entities/         // 数据库实体(User)
│  └─ Models/           // 公共模型(分页、导出参数)
├─ BusinessLogic/       // 业务逻辑层(BLL)
│  ├─ Interfaces/       // 服务接口(IUserService)
│  └─ Services/         // 服务实现(UserService)
├─ DataAccess/          // 数据访问层(DAL)
│  ├─ Contexts/         // EF Core上下文(支持SQL Server/MySQL)
│  ├─ Interfaces/       // 仓储接口(IUserRepository)
│  └─ Repositories/     // 仓储实现(UserRepository)
├─ Infrastructure/      // 基础设施层
│  ├─ Dependency/       // Autofac配置
│  ├─ Exception/        // 全局异常处理
│  ├─ Extensions/       // 扩展方法(Excel导出)
│  └─ Logging/          // 日志配置
├─ Resources/           // 资源文件(可选)
└─ Forms/               // UI层(主窗体+分页控件)
   └─ MainForm.cs       // 核心窗体(所有功能入口)

四、环境准备

4.1 NuGet包安装(必装)

打开VS2022 → 右键项目 → 管理NuGet程序包 → 安装以下包:

bash 复制代码
# EF Core 核心
Microsoft.EntityFrameworkCore 7.0.0
# SQL Server 驱动
Microsoft.EntityFrameworkCore.SqlServer 7.0.0
# MySQL 驱动(Pomelo)
Pomelo.EntityFrameworkCore.MySql 7.0.0
# Autofac
Autofac 7.1.0
# Excel导出
NPOI 2.6.0
# 日志
Serilog 3.1.1
Serilog.Sinks.File 5.0.0

4.2 数据库准备

  • SQL Server :新建空数据库(命名:UserManagementDB
  • MySQL :新建空数据库(命名:user_management_db,字符集:utf8mb4)

五、完整代码实现(复制即用)

5.1 Core层:实体与公共模型

5.1.1 核心实体(User)

Core/Entities/User.cs

csharp 复制代码
using System;

namespace WinFormThreeLayerDemo.Core.Entities
{
    /// <summary>
    /// 用户实体(对应数据库Users表)
    /// </summary>
    public class User
    {
        /// <summary>
        /// 主键ID
        /// </summary>
        public Guid Id { get; set; } = Guid.NewGuid();

        /// <summary>
        /// 用户名
        /// </summary>
        public string Username { get; set; } = string.Empty;

        /// <summary>
        /// 邮箱
        /// </summary>
        public string Email { get; set; } = string.Empty;

        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreateTime { get; set; } = DateTime.Now;
    }
}
5.1.2 分页模型

Core/Models/PaginationModel.cs

csharp 复制代码
using System;
using System.Collections.Generic;

namespace WinFormThreeLayerDemo.Core.Models
{
    /// <summary>
    /// 分页查询参数
    /// </summary>
    public class PaginationQuery
    {
        /// <summary>
        /// 当前页码(默认第1页)
        /// </summary>
        public int PageIndex { get; set; } = 1;

        /// <summary>
        /// 每页条数(默认10条)
        /// </summary>
        public int PageSize { get; set; } = 10;

        /// <summary>
        /// 搜索关键词(可选)
        /// </summary>
        public string Keyword { get; set; } = string.Empty;
    }

    /// <summary>
    /// 分页返回结果
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PaginationResult<T>
    {
        /// <summary>
        /// 总条数
        /// </summary>
        public int TotalCount { get; set; }

        /// <summary>
        /// 总页数
        /// </summary>
        public int TotalPages { get; set; }

        /// <summary>
        /// 当前页数据
        /// </summary>
        public List<T> Data { get; set; } = new List<T>();
    }
}

5.2 DataAccess层:数据访问(EF Core+双数据库)

5.2.1 EF Core上下文(支持SQL Server/MySQL)

DataAccess/Contexts/UserDbContext.cs

csharp 复制代码
using Microsoft.EntityFrameworkCore;
using WinFormThreeLayerDemo.Core.Entities;

namespace WinFormThreeLayerDemo.DataAccess.Contexts
{
    /// <summary>
    /// EF Core数据库上下文(支持SQL Server/MySQL切换)
    /// </summary>
    public class UserDbContext : DbContext
    {
        /// <summary>
        /// 映射Users表
        /// </summary>
        public DbSet<User> Users => Set<User>();

        /// <summary>
        /// 配置数据库连接(切换注释即可换数据库)
        /// </summary>
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // ========== 方案1:SQL Server 连接 ==========
            string sqlServerConn = "Data Source=.;Initial Catalog=UserManagementDB;User ID=sa;Password=你的SQL密码;TrustServerCertificate=True;";
            optionsBuilder.UseSqlServer(sqlServerConn);

            // ========== 方案2:MySQL 连接 ==========
            // string mySqlConn = "server=localhost;port=3306;database=user_management_db;uid=root;pwd=你的MySQL密码;charset=utf8mb4;";
            // optionsBuilder.UseMySql(mySqlConn, ServerVersion.AutoDetect(mySqlConn));
        }

        /// <summary>
        /// 配置实体映射(可选,EF Core可自动识别)
        /// </summary>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // 配置User实体主键
            modelBuilder.Entity<User>().HasKey(u => u.Id);
            // 配置Username非空
            modelBuilder.Entity<User>().Property(u => u.Username).IsRequired();
            // 配置Email非空
            modelBuilder.Entity<User>().Property(u => u.Email).IsRequired();
        }
    }
}
5.2.2 仓储接口(IUserRepository)

DataAccess/Interfaces/IUserRepository.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using WinFormThreeLayerDemo.Core.Entities;
using WinFormThreeLayerDemo.Core.Models;

namespace WinFormThreeLayerDemo.DataAccess.Interfaces
{
    /// <summary>
    /// 用户仓储接口(定义数据操作规范)
    /// </summary>
    public interface IUserRepository
    {
        /// <summary>
        /// 添加用户
        /// </summary>
        Task<User> AddUserAsync(User user);

        /// <summary>
        /// 根据ID查询用户
        /// </summary>
        Task<User?> GetUserByIdAsync(Guid id);

        /// <summary>
        /// 分页查询用户(支持搜索)
        /// </summary>
        Task<PaginationResult<User>> GetUsersByPageAsync(PaginationQuery query);

        /// <summary>
        /// 删除用户
        /// </summary>
        Task<bool> DeleteUserAsync(Guid id);

        /// <summary>
        /// 编辑用户
        /// </summary>
        Task<bool> UpdateUserAsync(User user);

        /// <summary>
        /// 导出所有用户(用于Excel)
        /// </summary>
        Task<List<User>> GetAllUsersForExportAsync();
    }
}
5.2.3 仓储实现(UserRepository)

DataAccess/Repositories/UserRepository.cs

csharp 复制代码
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WinFormThreeLayerDemo.Core.Entities;
using WinFormThreeLayerDemo.Core.Models;
using WinFormThreeLayerDemo.DataAccess.Contexts;
using WinFormThreeLayerDemo.DataAccess.Interfaces;

namespace WinFormThreeLayerDemo.DataAccess.Repositories
{
    /// <summary>
    /// 用户仓储实现(EF Core操作数据库)
    /// </summary>
    public class UserRepository : IUserRepository
    {
        private readonly UserDbContext _dbContext;

        public UserRepository(UserDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        /// <summary>
        /// 添加用户
        /// </summary>
        public async Task<User> AddUserAsync(User user)
        {
            await _dbContext.Users.AddAsync(user);
            await _dbContext.SaveChangesAsync();
            return user;
        }

        /// <summary>
        /// 根据ID查询用户
        /// </summary>
        public async Task<User?> GetUserByIdAsync(Guid id)
        {
            return await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == id);
        }

        /// <summary>
        /// 分页查询(支持模糊搜索)
        /// </summary>
        public async Task<PaginationResult<User>> GetUsersByPageAsync(PaginationQuery query)
        {
            // 构建查询(支持关键词搜索)
            var queryable = _dbContext.Users.AsQueryable();
            if (!string.IsNullOrWhiteSpace(query.Keyword))
            {
                queryable = queryable.Where(u => u.Username.Contains(query.Keyword) || u.Email.Contains(query.Keyword));
            }

            // 总条数
            int totalCount = await queryable.CountAsync();
            // 分页数据
            var data = await queryable
                .OrderByDescending(u => u.CreateTime) // 按创建时间倒序
                .Skip((query.PageIndex - 1) * query.PageSize)
                .Take(query.PageSize)
                .ToListAsync();

            return new PaginationResult<User>
            {
                TotalCount = totalCount,
                TotalPages = (int)Math.Ceiling(totalCount / (double)query.PageSize),
                Data = data
            };
        }

        /// <summary>
        /// 删除用户
        /// </summary>
        public async Task<bool> DeleteUserAsync(Guid id)
        {
            var user = await GetUserByIdAsync(id);
            if (user == null) return false;

            _dbContext.Users.Remove(user);
            return await _dbContext.SaveChangesAsync() > 0;
        }

        /// <summary>
        /// 编辑用户
        /// </summary>
        public async Task<bool> UpdateUserAsync(User user)
        {
            _dbContext.Users.Update(user);
            return await _dbContext.SaveChangesAsync() > 0;
        }

        /// <summary>
        /// 导出所有用户(Excel用)
        /// </summary>
        public async Task<List<User>> GetAllUsersForExportAsync()
        {
            return await _dbContext.Users.OrderByDescending(u => u.CreateTime).ToListAsync();
        }
    }
}

5.3 BusinessLogic层:业务逻辑

5.3.1 服务接口(IUserService)

BusinessLogic/Interfaces/IUserService.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using WinFormThreeLayerDemo.Core.Entities;
using WinFormThreeLayerDemo.Core.Models;

namespace WinFormThreeLayerDemo.BusinessLogic.Interfaces
{
    /// <summary>
    /// 用户业务服务接口
    /// </summary>
    public interface IUserService
    {
        /// <summary>
        /// 添加用户(带业务校验)
        /// </summary>
        Task<User> CreateUserAsync(string username, string email);

        /// <summary>
        /// 分页查询用户
        /// </summary>
        Task<PaginationResult<User>> GetUsersByPageAsync(PaginationQuery query);

        /// <summary>
        /// 删除用户
        /// </summary>
        Task<bool> DeleteUserAsync(Guid id);

        /// <summary>
        /// 编辑用户
        /// </summary>
        Task<bool> UpdateUserAsync(Guid id, string newUsername, string newEmail);

        /// <summary>
        /// 导出所有用户到Excel
        /// </summary>
        Task<byte[]> ExportUsersToExcelAsync();
    }
}
5.3.2 服务实现(UserService)

BusinessLogic/Services/UserService.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using WinFormThreeLayerDemo.BusinessLogic.Interfaces;
using WinFormThreeLayerDemo.Core.Entities;
using WinFormThreeLayerDemo.Core.Models;
using WinFormThreeLayerDemo.DataAccess.Interfaces;

namespace WinFormThreeLayerDemo.BusinessLogic.Services
{
    /// <summary>
    /// 用户业务服务实现(核心规则处理)
    /// </summary>
    public class UserService : IUserService
    {
        private readonly IUserRepository _userRepository;

        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        /// <summary>
        /// 添加用户(业务校验)
        /// </summary>
        public async Task<User> CreateUserAsync(string username, string email)
        {
            // 业务规则1:用户名不能为空
            if (string.IsNullOrWhiteSpace(username))
                throw new ArgumentException("用户名不能为空!");

            // 业务规则2:邮箱格式校验
            if (!email.Contains("@") || !email.Contains("."))
                throw new ArgumentException("邮箱格式不正确(需包含@和.)!");

            // 封装实体
            var user = new User
            {
                Username = username.Trim(),
                Email = email.Trim()
            };

            // 调用数据层添加
            return await _userRepository.AddUserAsync(user);
        }

        /// <summary>
        /// 分页查询用户(转发到数据层)
        /// </summary>
        public async Task<PaginationResult<User>> GetUsersByPageAsync(PaginationQuery query)
        {
            return await _userRepository.GetUsersByPageAsync(query);
        }

        /// <summary>
        /// 删除用户(转发到数据层)
        /// </summary>
        public async Task<bool> DeleteUserAsync(Guid id)
        {
            return await _userRepository.DeleteUserAsync(id);
        }

        /// <summary>
        /// 编辑用户(带业务校验)
        /// </summary>
        public async Task<bool> UpdateUserAsync(Guid id, string newUsername, string newEmail)
        {
            // 业务校验
            if (string.IsNullOrWhiteSpace(newUsername))
                throw new ArgumentException("用户名不能为空!");
            if (!newEmail.Contains("@") || !newEmail.Contains("."))
                throw new ArgumentException("邮箱格式不正确(需包含@和.)!");

            // 查询用户是否存在
            var user = await _userRepository.GetUserByIdAsync(id);
            if (user == null)
                throw new KeyNotFoundException("用户不存在,无法编辑!");

            // 更新信息
            user.Username = newUsername.Trim();
            user.Email = newEmail.Trim();

            // 调用数据层更新
            return await _userRepository.UpdateUserAsync(user);
        }

        /// <summary>
        /// 导出用户到Excel(NPOI实现)
        /// </summary>
        public async Task<byte[]> ExportUsersToExcelAsync()
        {
            // 获取所有用户
            var users = await _userRepository.GetAllUsersForExportAsync();

            // 创建Excel工作簿
            IWorkbook workbook = new XSSFWorkbook();
            ISheet sheet = workbook.CreateSheet("用户列表");

            // 创建表头
            IRow headerRow = sheet.CreateRow(0);
            headerRow.CreateCell(0).SetCellValue("用户ID");
            headerRow.CreateCell(1).SetCellValue("用户名");
            headerRow.CreateCell(2).SetCellValue("邮箱");
            headerRow.CreateCell(3).SetCellValue("创建时间");

            // 填充数据
            int rowIndex = 1;
            foreach (var user in users)
            {
                IRow row = sheet.CreateRow(rowIndex++);
                row.CreateCell(0).SetCellValue(user.Id.ToString());
                row.CreateCell(1).SetCellValue(user.Username);
                row.CreateCell(2).SetCellValue(user.Email);
                row.CreateCell(3).SetCellValue(user.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"));
            }

            // 自动调整列宽
            for (int i = 0; i < 4; i++)
            {
                sheet.AutoSizeColumn(i);
            }

            // 转换为字节数组(用于保存文件)
            using (MemoryStream ms = new MemoryStream())
            {
                workbook.Write(ms);
                return ms.ToArray();
            }
        }
    }
}

5.4 Infrastructure层:基础设施

5.4.1 Autofac依赖注入配置

Infrastructure/Dependency/AutofacConfig.cs

csharp 复制代码
using Autofac;
using WinFormThreeLayerDemo.BusinessLogic.Interfaces;
using WinFormThreeLayerDemo.BusinessLogic.Services;
using WinFormThreeLayerDemo.DataAccess.Contexts;
using WinFormThreeLayerDemo.DataAccess.Interfaces;
using WinFormThreeLayerDemo.DataAccess.Repositories;

namespace WinFormThreeLayerDemo.Infrastructure.Dependency
{
    /// <summary>
    /// Autofac依赖注入配置
    /// </summary>
    public static class AutofacConfig
    {
        /// <summary>
        /// 构建Autofac容器
        /// </summary>
        public static IContainer BuildContainer()
        {
            var builder = new ContainerBuilder();

            // 注册EF Core上下文(单例)
            builder.RegisterType<UserDbContext>().SingleInstance();

            // 注册数据访问层:接口→实现
            builder.RegisterType<UserRepository>().As<IUserRepository>().InstancePerLifetimeScope();

            // 注册业务逻辑层:接口→实现
            builder.RegisterType<UserService>().As<IUserService>().InstancePerLifetimeScope();

            // 注册主窗体
            builder.RegisterType<MainForm>().InstancePerDependency();

            return builder.Build();
        }
    }
}
5.4.2 全局异常处理+日志

Infrastructure/Exception/GlobalExceptionHandler.cs

csharp 复制代码
using Serilog;
using System;
using System.Windows.Forms;

namespace WinFormThreeLayerDemo.Infrastructure.Exception
{
    /// <summary>
    /// 全局异常处理器(统一捕获+弹窗+日志)
    /// </summary>
    public static class GlobalExceptionHandler
    {
        /// <summary>
        /// 初始化全局异常处理
        /// </summary>
        public static void Init()
        {
            // 配置Serilog日志(输出到文件,按天分割)
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Error() // 只记录错误日志
                .WriteTo.File(
                    path: "Logs/Error-.log",
                    rollingInterval: RollingInterval.Day,
                    outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"
                )
                .CreateLogger();

            // 捕获UI线程异常
            Application.ThreadException += (sender, e) =>
            {
                HandleException(e.Exception, "UI线程异常");
            };

            // 捕获非UI线程异常
            AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
            {
                if (e.ExceptionObject is System.Exception ex)
                {
                    HandleException(ex, "非UI线程异常");
                }
            };
        }

        /// <summary>
        /// 统一处理异常
        /// </summary>
        /// <param name="ex">异常对象</param>
        /// <param name="source">异常来源</param>
        public static void HandleException(System.Exception ex, string source)
        {
            // 写入日志
            Log.Error(ex, $"{source}:{ex.Message}");

            // 友好弹窗提示
            string message = $"【系统提示】\n{ex.Message}\n\n详情请查看Logs文件夹下的日志文件!";
            MessageBox.Show(message, "操作失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}

5.5 Program.cs:程序入口

csharp 复制代码
using Autofac;
using System;
using System.Windows.Forms;
using WinFormThreeLayerDemo.Infrastructure.Dependency;
using WinFormThreeLayerDemo.Infrastructure.Exception;

namespace WinFormThreeLayerDemo
{
    internal static class Program
    {
        /// <summary>
        /// 应用程序的主入口点
        /// </summary>
        [STAThread]
        static void Main()
        {
            // 1. 初始化全局异常处理
            GlobalExceptionHandler.Init();

            // 2. 启用Windows视觉样式
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            // 3. 构建Autofac容器并解析主窗体
            var container = AutofacConfig.BuildContainer();
            using (var scope = container.BeginLifetimeScope())
            {
                var mainForm = scope.Resolve<MainForm>();
                Application.Run(mainForm);
            }
        }
    }
}

5.6 MainForm:核心窗体(完整功能)

5.6.1 窗体控件布局(必看!)
控件类型 控件Name 显示文本 用途
TextBox txtUsername 新增/编辑用户名输入
TextBox txtEmail 新增/编辑邮箱输入
TextBox txtSearch 搜索关键词输入
Button btnAdd 新增用户 触发新增功能
Button btnEdit 编辑选中 触发编辑功能
Button btnDelete 删除选中 触发删除功能
Button btnSearch 搜索 触发搜索功能
Button btnExport 导出Excel 触发Excel导出
Button btnFirstPage 首页 分页-首页
Button btnPrevPage 上一页 分页-上一页
Button btnNextPage 下一页 分页-下一页
Button btnLastPage 尾页 分页-尾页
Label lblPageInfo 显示分页信息(共X页/当前第X页)
DataGridView dgvUserList - 用户列表展示
Label lblTip 操作提示(红色字体)
5.6.2 窗体完整代码

MainForm.cs

csharp 复制代码
using System;
using System.IO;
using System.Windows.Forms;
using WinFormThreeLayerDemo.BusinessLogic.Interfaces;
using WinFormThreeLayerDemo.Core.Entities;
using WinFormThreeLayerDemo.Core.Models;
using WinFormThreeLayerDemo.Infrastructure.Exception;

namespace WinFormThreeLayerDemo
{
    public partial class MainForm : Form
    {
        // 业务服务(Autofac注入)
        private readonly IUserService _userService;
        // 当前分页参数
        private PaginationQuery _currentQuery = new PaginationQuery();
        // 当前编辑的用户ID
        private Guid _editUserId = Guid.Empty;

        /// <summary>
        /// 构造函数(Autofac自动注入IUserService)
        /// </summary>
        public MainForm(IUserService userService)
        {
            InitializeComponent();
            _userService = userService;

            // 初始化DataGridView
            InitDataGridView();
            // 加载第一页数据
            LoadUserListAsync();
        }

        /// <summary>
        /// 初始化DataGridView(自适应+列配置)
        /// </summary>
        private void InitDataGridView()
        {
            // 清空默认列
            dgvUserList.Columns.Clear();

            // 添加列(绑定User实体属性)
            dgvUserList.Columns.Add(new DataGridViewTextBoxColumn
            {
                Name = "colId",
                HeaderText = "用户ID",
                DataPropertyName = "Id",
                Width = 180
            });
            dgvUserList.Columns.Add(new DataGridViewTextBoxColumn
            {
                Name = "colUsername",
                HeaderText = "用户名",
                DataPropertyName = "Username",
                AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells
            });
            dgvUserList.Columns.Add(new DataGridViewTextBoxColumn
            {
                Name = "colEmail",
                HeaderText = "邮箱",
                DataPropertyName = "Email",
                AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells
            });
            dgvUserList.Columns.Add(new DataGridViewTextBoxColumn
            {
                Name = "colCreateTime",
                HeaderText = "创建时间",
                DataPropertyName = "CreateTime",
                DefaultCellStyle = { Format = "yyyy-MM-dd HH:mm:ss" },
                Width = 180
            });

            // 样式配置
            dgvUserList.ReadOnly = true; // 只读
            dgvUserList.SelectionMode = DataGridViewSelectionMode.FullRowSelect; // 整行选中
            dgvUserList.AllowUserToAddRows = false; // 禁止新增行
            dgvUserList.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; // 自适应窗体
        }

        /// <summary>
        /// 加载用户列表(分页)
        /// </summary>
        private async void LoadUserListAsync()
        {
            try
            {
                lblTip.Text = "正在加载数据...";
                // 调用业务层获取分页数据
                var result = await _userService.GetUsersByPageAsync(_currentQuery);
                // 绑定到DataGridView
                dgvUserList.DataSource = result.Data;
                // 更新分页信息
                lblPageInfo.Text = $"共{result.TotalCount}条数据 | 共{result.TotalPages}页 | 当前第{_currentQuery.PageIndex}页";
                // 禁用/启用分页按钮
                btnFirstPage.Enabled = _currentQuery.PageIndex > 1;
                btnPrevPage.Enabled = _currentQuery.PageIndex > 1;
                btnNextPage.Enabled = _currentQuery.PageIndex < result.TotalPages;
                btnLastPage.Enabled = _currentQuery.PageIndex < result.TotalPages;

                lblTip.Text = "加载完成!";
            }
            catch (System.Exception ex)
            {
                GlobalExceptionHandler.HandleException(ex, "加载用户列表");
                lblTip.Text = "加载失败!";
            }
        }

        /// <summary>
        /// 新增用户按钮点击事件
        /// </summary>
        private async void btnAdd_Click(object sender, EventArgs e)
        {
            try
            {
                // 获取输入值
                string username = txtUsername.Text.Trim();
                string email = txtEmail.Text.Trim();

                // 调用业务层新增
                await _userService.CreateUserAsync(username, email);

                // 清空输入框
                txtUsername.Text = string.Empty;
                txtEmail.Text = string.Empty;
                // 刷新列表(回到第一页)
                _currentQuery.PageIndex = 1;
                LoadUserListAsync();

                lblTip.Text = "新增用户成功!";
            }
            catch (System.Exception ex)
            {
                GlobalExceptionHandler.HandleException(ex, "新增用户");
                lblTip.Text = "新增失败!";
            }
        }

        /// <summary>
        /// 编辑选中用户按钮点击事件
        /// </summary>
        private void btnEdit_Click(object sender, EventArgs e)
        {
            // 检查是否选中行
            if (dgvUserList.SelectedRows.Count == 0)
            {
                lblTip.Text = "请先选中要编辑的用户!";
                return;
            }

            // 获取选中用户信息
            var selectedUser = (User)dgvUserList.SelectedRows[0].DataBoundItem;
            // 填充到输入框
            txtUsername.Text = selectedUser.Username;
            txtEmail.Text = selectedUser.Email;
            // 记录编辑的用户ID
            _editUserId = selectedUser.Id;

            lblTip.Text = "请修改信息后点击【新增用户】按钮保存!";
        }

        /// <summary>
        /// 删除选中用户按钮点击事件
        /// </summary>
        private async void btnDelete_Click(object sender, EventArgs e)
        {
            // 检查是否选中行
            if (dgvUserList.SelectedRows.Count == 0)
            {
                lblTip.Text = "请先选中要删除的用户!";
                return;
            }

            // 确认删除
            if (MessageBox.Show("确定要删除选中的用户吗?此操作不可恢复!", "确认删除", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)
            {
                return;
            }

            try
            {
                // 获取选中用户ID
                var selectedUser = (User)dgvUserList.SelectedRows[0].DataBoundItem;
                // 调用业务层删除
                bool isSuccess = await _userService.DeleteUserAsync(selectedUser.Id);

                if (isSuccess)
                {
                    lblTip.Text = "删除用户成功!";
                    // 刷新列表
                    LoadUserListAsync();
                }
                else
                {
                    lblTip.Text = "删除失败:用户不存在!";
                }
            }
            catch (System.Exception ex)
            {
                GlobalExceptionHandler.HandleException(ex, "删除用户");
                lblTip.Text = "删除失败!";
            }
        }

        /// <summary>
        /// 搜索按钮点击事件
        /// </summary>
        private void btnSearch_Click(object sender, EventArgs e)
        {
            // 更新搜索关键词
            _currentQuery.Keyword = txtSearch.Text.Trim();
            // 回到第一页
            _currentQuery.PageIndex = 1;
            // 刷新列表
            LoadUserListAsync();

            lblTip.Text = "搜索完成!";
        }

        /// <summary>
        /// 导出Excel按钮点击事件
        /// </summary>
        private async void btnExport_Click(object sender, EventArgs e)
        {
            try
            {
                lblTip.Text = "正在导出Excel...";
                // 调用业务层生成Excel字节数组
                byte[] excelBytes = await _userService.ExportUsersToExcelAsync();

                // 保存文件对话框
                using (SaveFileDialog sfd = new SaveFileDialog())
                {
                    sfd.Filter = "Excel文件 (*.xlsx)|*.xlsx";
                    sfd.FileName = $"用户列表_{DateTime.Now:yyyyMMddHHmmss}.xlsx";
                    if (sfd.ShowDialog() == DialogResult.OK)
                    {
                        // 写入文件
                        File.WriteAllBytes(sfd.FileName, excelBytes);
                        lblTip.Text = "Excel导出成功!";
                        MessageBox.Show("Excel导出成功!文件路径:" + sfd.FileName, "导出成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                }
            }
            catch (System.Exception ex)
            {
                GlobalExceptionHandler.HandleException(ex, "导出Excel");
                lblTip.Text = "导出失败!";
            }
        }

        #region 分页按钮事件
        /// <summary>
        /// 首页
        /// </summary>
        private void btnFirstPage_Click(object sender, EventArgs e)
        {
            _currentQuery.PageIndex = 1;
            LoadUserListAsync();
        }

        /// <summary>
        /// 上一页
        /// </summary>
        private void btnPrevPage_Click(object sender, EventArgs e)
        {
            if (_currentQuery.PageIndex > 1)
            {
                _currentQuery.PageIndex--;
                LoadUserListAsync();
            }
        }

        /// <summary>
        /// 下一页
        /// </summary>
        private void btnNextPage_Click(object sender, EventArgs e)
        {
            _currentQuery.PageIndex++;
            LoadUserListAsync();
        }

        /// <summary>
        /// 尾页
        /// </summary>
        private void btnLastPage_Click(object sender, EventArgs e)
        {
            // 先获取总页数
            Task.Run(async () =>
            {
                var result = await _userService.GetUsersByPageAsync(_currentQuery);
                _currentQuery.PageIndex = result.TotalPages;
                // 切换到UI线程刷新
                this.Invoke(new Action(LoadUserListAsync));
            });
        }
        #endregion
    }
}

六、数据库迁移(生成表结构)

  1. 打开VS2022 → 工具 → NuGet包管理器 → 包管理器控制台;
  2. 执行以下命令(生成建表代码并执行):
powershell 复制代码
# 初始化迁移(生成建表脚本)
Add-Migration InitialCreate
# 执行迁移(创建数据库表)
Update-Database
  1. 验证:打开SQL Server/MySQL,查看Users表是否创建成功。

七、运行教程(一步到位)

  1. 替换UserDbContext.cs中的数据库连接字符串(修改为你的账号密码);
  2. 执行数据库迁移(生成Users表);
  3. 按F5启动项目;
  4. 功能测试:
    • 新增用户:输入用户名+邮箱 → 点击【新增用户】;
    • 编辑用户:选中列表行 → 点击【编辑选中】→ 修改信息 → 点击【新增用户】保存;
    • 删除用户:选中列表行 → 点击【删除选中】→ 确认;
    • 搜索用户:输入关键词 → 点击【搜索】;
    • 分页:点击【上一页/下一页/首页/尾页】切换;
    • 导出Excel:点击【导出Excel】→ 选择保存路径。

八、配套资源(免费获取)

8.1 完整源码

关注后私信「WinForm三层架构」,自动获取:

  • 完整项目源码(VS2022可直接打开);
  • 数据库脚本(SQL Server/MySQL手动建表语句);
  • 窗体布局截图(控件位置参考)。

8.2 常见问题解决

问题 解决方案
数据库连接失败 检查连接字符串中的服务器名、账号、密码;SQL Server需开启TCP/IP协议;MySQL需允许远程连接
迁移命令报错 确保安装了Microsoft.EntityFrameworkCore.Tools包;设置项目为启动项目
Excel导出失败 确保安装了NPOI 2.6.0包;目标文件夹有写入权限
Autofac注入报错 检查AutofacConfig中接口和实现类是否匹配;确保MainForm构造函数参数正确

九、进阶扩展方向

  1. 用户登录/权限控制:添加LoginForm+Role实体+权限校验;
  2. 多条件查询:扩展PaginationQuery,支持按时间范围/邮箱域名查询;
  3. 批量操作:批量删除/批量导入Excel;
  4. 数据缓存:添加MemoryCache缓存用户列表,提升查询性能;
  5. 本地化:支持中英文切换;
  6. 皮肤美化:集成DevExpress/MetroFramework美化界面。

十、总结

这套项目是企业级WinForm开发的标准模板,核心亮点:

  1. 分层清晰:UI只做交互、BLL只做规则、DAL只做数据,解耦易维护;
  2. 功能完整:增删改查+分页+搜索+导出,满足大部分小型桌面工具需求;
  3. 企业特性:依赖注入+全局异常+日志,符合工业级开发规范;
  4. 双数据库:一键切换SQL Server/MySQL,适配不同环境。
相关推荐
观测云1 小时前
KES(KingbaseES)数据库监控最佳实践
数据库
奇点爆破XC1 小时前
Redis迁移
数据库·redis·bootstrap
断手当码农2 小时前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
菜鸟小九2 小时前
redis原理篇(基本数据结构)
数据结构·数据库·redis
没有bug.的程序员2 小时前
电商秒杀系统深度进阶:高并发流量建模、库存零超卖内核与 Redis+MQ 闭环
数据库·redis·缓存·高并发·电商秒杀·流量建模·库存零超卖
绝无仅有2 小时前
Java多线程并发问题解决方案全解析
后端·面试·架构
先做个垃圾出来………2 小时前
Python常见文件操作
linux·数据库·python
够快云库2 小时前
制造业非结构化数据治理:架构解析与实战复盘
大数据·人工智能·架构·企业文件安全
轩情吖2 小时前
MySQL库的操作
android·数据库·mysql·oracle·字符集·数据库操作·编码集