欢迎光临
我们一直在努力

WinForm 综合实战:基于三层架构的 MySchool 学校信息管理系统开发指南——登录模块及学生信息管理模块

本周主要学习了用Winform开发学生管理系统,主要用于演示 WinForm 应用程序的开发流程和关键技术。

一、项目概述

MySchool 是一个典型的 Windows Forms(WinForm)桌面应用,用于管理学校基础信息,涵盖学生、教师、课程、班级、年级等模块,并包含用户登录与权限控制。它不仅是教学案例,更是学习 WinForm + 三层架构 + ADO.NET 的经典综合项目。

它包括以下核心模块:

  • 学生信息管理 – 学生档案的增删改查(已完成)

  • 教师信息管理 – 教师基本信息管理

  • 课程管理 – 课程设置与安排

  • 年级管理 – 年级信息维护

  • 班级管理 – 班级信息维护

  • 用户权限管理 – 系统登录与权限控制

  • 目前我只完成了学生信息管理部分以及登录页面;

    二、技术架构

    项目采用清晰的 三层架构(3-tier architecture),实现关注点分离:

    层级项目名职责
    表示层(UI) MySchool.UI WinForm 界面、用户交互、数据绑定
    业务逻辑层(BLL) MySchool.BLL 业务规则、数据校验、调用 DAL
    数据访问层(DAL) MySchool.DAL 数据库操作(ADO.NET)、SQL 执行
    模型层(共享) MySchool.Models 实体类(如 Student、Teacher)
    工具层(可选) MySchool.Utils

    全局工具(如当前登录用户上下文)

    依赖关系:

    UI 层→ BLL层 + Models层
    BLL层 → DAL层 + Models层
    DAL层 → Models层

    基本项目搭建:

    第一步: 创建一个MySchool的解决方案, 项目名为MySchool.UI , 项目模版: Windows窗体应用(.Net Framework)

    图1

    第二步: 在MySchool的解决方案,右键创建项目, 模型选择:类库(.Net Framework)

    图2

    依次创建MySchool.DAL MySchool.BLL

    完整的项目结构如下:

    图3

    第三步: 为相同添加其他项目的依赖:

    • MySchool.UI 依赖MySchool.BLL和MySchool.Models

    • MySchool.BLL 依赖MySchool.DAL和MySchool.Models

    • MySchool.DAL 依赖MySchool.Models

    图4

    三、关键技术点详解

     数据库设计

    • 使用 SQL Server
    • 数据库  MySchool
    • 主要表:Admin、Student、Teacher、Class、Grade、Subject、UserState
    • 外键关联(如 Student.ClassId → Class.ClassId)

    图5

    连接字符串配置

    不要使用硬编码,可以写在App.config中;

    图6

    在项目中添加配置类库 ,通过在MySchool.DAL项目中创建数据库工具类: DBHelper.cs,并添加  ConfigurationManager.ConnectionStrings["MySchool"] 来读取

    如图:

    图7

    数据库帮助类相关代码:

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace MySchool.DAL
    {
    /// <summary>
    /// 数据库辅助类
    /// </summary>
    public class DBHelper
    {
    //获取数据库连接字符串
    static string ConnStr = System.Configuration.ConfigurationManager.ConnectionStrings["MySchool"].ToString();

    #region 增、删、改(普通的SQL语句)
    /// <summary>
    /// 增、删、改(普通的SQL语句)
    /// </summary>
    /// <param name="sql">普通的SQL语句</param>
    /// <returns>返回的受影响的行数/returns>
    public static int CUD(string sql)
    {

    SqlConnection con = null;
    try
    {
    //创建数据库连接对象
    con = new SqlConnection(ConnStr);
    //打开数据库连接
    con.Open();
    //创建命令对象
    SqlCommand com = new SqlCommand(sql, con);
    int index = com.ExecuteNonQuery();
    return index;

    }
    catch (Exception ex)
    {

    return -1;
    }
    finally
    {
    con.Close();
    }
    }

    #endregion

    #region 增、删、改(参数化SQL语句)
    /// <summary>
    /// 增、删、改(参数化SQL语句)
    /// </summary>
    /// <param name="sql">参数化SQL语句</param>
    /// <returns>返回的受影响的行数/returns>
    public static int CUD(string sql, List<SqlParameter> sqlParams)
    {
    SqlConnection con = null;
    try
    {
    //创建数据库连接对象
    con = new SqlConnection(ConnStr);
    //打开数据库连接
    con.Open();
    //创建命令对象
    SqlCommand com = new SqlCommand(sql, con);
    //ToArray()用于将集合转换为数组
    com.Parameters.AddRange(sqlParams.ToArray());
    int index = com.ExecuteNonQuery();
    return index;

    }
    catch (Exception ex)
    {

    return -1;
    }
    finally
    {
    con.Close();
    }
    }

    #endregion

    #region 执行查询操作,返回SqlDataReader对象
    /// <summary>
    /// 执行查询操作,返回SqlDataReader对象
    /// </summary>
    /// <param name="sql">普通的SQL语句</param>
    /// <returns>返回的SqlDataReader对象</returns>

    public static SqlDataReader GetRead(string sql)
    {
    Console.WriteLine(ConnStr);
    SqlConnection con = null;
    try
    {
    con = new SqlConnection(ConnStr);
    con.Open();
    SqlCommand com = new SqlCommand(sql, con);
    //CommandBehavior.CloseConnection用于设置当SqlDataReader关闭时会自动关闭连接
    SqlDataReader dr = com.ExecuteReader(CommandBehavior.CloseConnection);

    //SqlDataReader dr = com.ExecuteReader();
    return dr;

    }
    catch (Exception ex)
    {

    return null;

    }
    //finally
    //{
    // con.Close();
    //}

    }
    #endregion

    #region 执行查询操作,返回SqlDataReader对象(参数化SQL语句)
    /// <summary>
    /// 执行查询操作,返回SqlDataReader对象(参数化SQL语句)
    /// </summary>
    /// <param name="sql">参数化SQL语句</param>
    /// <returns>返回的SqlDataReader对象</returns>

    public static SqlDataReader GetRead(string sql, List<SqlParameter> parameters)
    {
    SqlConnection con = null;
    try
    {
    con = new SqlConnection(ConnStr);
    con.Open();
    SqlCommand com = new SqlCommand(sql, con);
    com.Parameters.AddRange(parameters.ToArray());
    //CommandBehavior.CloseConnection用于设置当SqlDataReader关闭时会自动关闭连接
    SqlDataReader dr = com.ExecuteReader(CommandBehavior.CloseConnection);
    return dr;

    }
    catch (Exception ex)
    {

    return null;
    }
    //finally
    //{
    // con.Close();
    //}
    }
    #endregion

    #region 执行查询操作,返回DataTable对象(普通SQL语句)
    /// <summary>
    /// 执行查询操作,返回DataTable对象(普通SQL语句)
    /// </summary>
    /// <param name="sql">普通sql语句</param>
    /// <returns>返回DataTable</returns>
    public static DataTable GetDateTable(string sql)
    {
    SqlConnection con = null;
    try
    {
    con = new SqlConnection(ConnStr);
    con.Open();
    SqlDataAdapter da = new SqlDataAdapter(sql, con);
    DataTable dt = new DataTable();
    da.Fill(dt);
    return dt;

    }
    catch (Exception ex)
    {

    return null;
    }
    finally
    {
    con.Close();
    }

    }
    #endregion

    #region 执行查询操作,返回DataTable对象(参数化SQL语句)
    /// <summary>
    /// 执行查询操作,返回DataTable对象(参数化SQL语句)
    /// </summary>
    /// <param name="sql">带@参数化SQL语句</param>
    /// <returns>返回DataTable</returns>
    public static DataTable GetDateTable(string sql, List<SqlParameter> parameters)
    {
    SqlConnection con = null;
    try
    {
    con = new SqlConnection(ConnStr);
    con.Open();
    SqlCommand com = new SqlCommand(sql, con);
    com.Parameters.AddRange(parameters.ToArray());
    SqlDataAdapter da = new SqlDataAdapter();
    da.SelectCommand = com;

    DataTable dt = new DataTable();
    da.Fill(dt);
    return dt;

    }
    catch (Exception ex)
    {

    return null;
    }
    finally
    {
    con.Close();
    }

    }
    #endregion

    #region 执行查询语句,返回第一行第一个单元格中的数据(普通SQL语句)
    /// <summary>
    /// 执行查询语句,返回第一行第一个单元格中的数据
    /// </summary>
    /// <param name="sql">普通的SQL语句</param>
    /// <returns>返回第一行第一个单元格中的数据</returns>
    public static object GetScalar(string sql)
    {

    SqlConnection con = null;
    try
    {
    con = new SqlConnection(ConnStr);
    con.Open();
    SqlCommand com = new SqlCommand(sql, con);
    object result = com.ExecuteScalar();
    return result;
    }
    catch (Exception ex)
    {
    return null;
    }
    finally
    {
    con.Close();
    }
    }
    #endregion

    #region 执行查询语句,返回第一行第一个单元格中的数据(参数化SQL语句)
    /// <summary>
    /// 执行查询语句,返回第一行第一个单元格中的数据(参数化SQL语句)
    /// </summary>
    /// <param name="sql">参数化SQL语句</param>
    /// <returns>返回第一行第一个单元格中的数据</returns>
    public static object GetScalar(string sql, List<SqlParameter> parameters)
    {

    SqlConnection con = null;
    try
    {
    con = new SqlConnection(ConnStr);
    con.Open();
    SqlCommand com = new SqlCommand(sql, con);
    com.Parameters.AddRange(parameters.ToArray());
    object result = com.ExecuteScalar();
    return result;
    }
    catch (Exception ex)
    {
    return null;
    }
    finally
    {
    con.Close();
    }
    }
    #endregion
    }
    }

    封装了常用 ADO.NET 操作:

    • CUD():执行增删改(支持参数化防 SQL 注入)
    • GetDataTable() / GetRead():查询返回 DataTable 或 SqlDataReader
    • GetScalar():获取单值(如 COUNT)

    并且使用 using 或 try-finally 确保连接关闭,避免资源泄漏。

    MDI多文档界面

    • 主窗体设为 IsMdiContainer = true
    • 子窗体通过 MdiParent = this 嵌入
    • 防重复打开:遍历 MdiChildren 判断是否已存在同名窗体

    开发遇见的问题:

    当我们对学员列表点击多次, 那学员列表就会打开多个, 怎么避免重复打开同一个子窗口

    解决方案: 检查是否已经打开了此MDI窗体,就弹窗提示,并结束;避免重复打开同一个子窗口

    代码示例:

    private void studentListToolStripMenuItem_Click(object sender, EventArgs e)
    {
    // 检查是否已经打开了此MDI窗体,避免重复打开同一个子窗口
    foreach (Form childrenForm in this.MdiChildren)
    {
    // 检查是不是当前子窗体名称
    if (childrenForm.Name == "StudentListForm")
    {
    // 是则显示,并激活该窗体
    childrenForm.Visible = true;
    childrenForm.Activate();

    MessageBox.Show(childrenForm.Name + "已经打开,请勿重复打开同一个子窗口");
    return;
    }
    }
    StudentListForm studentListForm = new StudentListForm();
    studentListForm.MdiParent = this; // 设置为主窗体的子窗体
    studentListForm.Show(); // 显示子窗体
    }

    四、登录流程(含第三方UI)

    使用SunnyUI的UILoginForm快速构建美观登录页

    方法:

    图8

    把继承基类的名字由Form改成UILoginForm

    然后我们的登录页面就变成了SunnyUI的登录页面,此方法用于快速构建登录页面,你也可以不用它的,自己设计,也许会更好看;

    图9

    登录验证流程:

    UI层→BLL.LoginO→ DAL查询 Admin 表

    成功后将用户信息存入全局上下文(Admincontext.CurrentAdmin)

    主界面通过该上下文显示当前用户名(如状态栏)

    三层架构中的角色和职责:

    层级项目类/方法职责
    表示层 (UI) MySchool.UI LoginForm.cs
    MainForm.cs
    接收用户输入
    调用 BLL 登录方法
    处理登录结果(跳转/提示)
    保存当前用户到全局上下文
    业务逻辑层 (BLL) MySchool.BLL AdminBLL.Login(string loginId, string pwd) 参数校验
    调用 DAL 获取用户
    比对密码(明文 or 加密)
    返回验证结果
    数据访问层 (DAL) MySchool.DAL AdminDAL.GetAdminByLoginId(string loginId) 执行 SQL 查询
    从 SqlDataReader 构造 AdminModel
    返回实体或 null
    模型层 MySchool.Models AdminModel 封装 Admin 表字段(LoginId, LoginPwd, UserName 等)
    工具层 MySchool.Utils AdminContext.CurrentAdmin 静态属性,保存当前登录用户

    关键代码实现:

    1.模型层:AdminModel.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace MySchool.Models
    {
    //internal: 本项目可见
    //public 公开的
    public class AdminModel
    {
    public AdminModel()
    {
    }

    public AdminModel(int adminId, string loginId, string loginPwd)
    {
    AdminId = adminId;
    LoginId = loginId;
    LoginPwd = loginPwd;
    }

    public int AdminId { get; set; }
    public string LoginId { get; set; }
    public string LoginPwd { get; set; }

    public override string ToString()
    {
    return $"AdminId:{AdminId},LoginId:{LoginId},LoginPwd:{LoginPwd}";
    }
    }
    }

    2.数据访问层:AdminDAL.cs

    using MySchool.Models;
    using System;
    using System.Collections.Generic;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace MySchool.DAL
    {
    /// <summary>
    /// Admin的数据访问类
    /// </summary>
    public class AdminDAL
    {
    /// <summary>
    /// 根据主键查询
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public AdminModel QueryById(int? id)
    {
    string sql = "select * from admin where adminId = @Id";
    List<SqlParameter> sqlParameters = new List<SqlParameter>() {
    new SqlParameter("Id",id)
    };
    using (SqlDataReader dataReader = DBHelper.GetRead(sql, sqlParameters)) {
    AdminModel admin = null;
    if (dataReader.Read())
    {
    //创建对象
    admin = new AdminModel();
    //给属性赋值
    admin.AdminId = Convert.ToInt32(dataReader["adminId"]);
    admin.LoginId = dataReader["loginId"].ToString();
    admin.LoginPwd = dataReader["LoginPwd"].ToString();

    }
    return admin;
    }

    }
    public AdminModel QueryByUserName(string userName)
    {
    string sql = "select * from admin where loginId = @username";
    List<SqlParameter> sqlParameters = new List<SqlParameter>() {
    new SqlParameter("username",userName)
    };
    using (SqlDataReader dataReader = DBHelper.GetRead(sql, sqlParameters))
    {
    AdminModel admin = null;
    if (dataReader.Read())
    {
    //创建对象
    admin = new AdminModel();
    //给属性赋值
    admin.AdminId = Convert.ToInt32(dataReader["adminId"]);
    admin.LoginId = dataReader["loginId"].ToString();
    admin.LoginPwd = dataReader["LoginPwd"].ToString();

    }
    return admin;
    }
    }
    }
    }

    使用 SqlParameter 防止 SQL 注入。

    3.业务逻辑层  AdminBLL.cs

    using MySchool.DAL;
    using MySchool.Models;
    using MySchool.Utils;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace MySchool.BLL
    {
    /// <summary>
    /// Admin业务类
    /// </summary>
    public class AdminBLL
    {
    private AdminDAL adminDAL = new AdminDAL();
    public AdminModel FindById(int? id)
    {
    //数据校验
    if (id == null)
    {
    throw new ArgumentNullException("id不能为空");
    }
    return adminDAL.QueryById(id);
    }

    // 密码存储在数据库, 加密的
    /// <summary>
    /// 登录方法
    /// </summary>
    /// <param name="userName">用户名</param>
    /// <param name="password">密码</param>
    /// <returns>true:登录成功, false: 失败</returns>
    public bool Login(string userName, string password)
    {
    AdminModel admin = adminDAL.QueryByUserName(userName);
    if (admin != null)
    {
    //判断密码是否一样
    if (admin.LoginPwd == password) {
    //把登录Admin信息保存
    UserContext.LoginUser = admin;
    return true;
    }

    }
    return false;
    }
    }
    }

    表示层(UI层):LoginForm.cs(使用 SunnyUI)

    图10

    全局用户上下文:UserContext.cs   MySchool.Utils层

    using MySchool.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Text;
    using System.Threading.Tasks;

    namespace MySchool.Utils
    {
    /// <summary>
    /// 存储登录的用户信息的帮助类
    /// </summary>
    public static class UserContext
    {
    /// <summary>
    /// 私有的镜头字段
    /// </summary>
    private static AdminModel _loginUser;
    /// <summary>
    /// 静态属性, 存储的是当前登录用户信息
    /// </summary>
    public static AdminModel LoginUser
    {
    get
    {
    return _loginUser;
    }
    set
    {
    _loginUser = value;

    }
    }
    }
    }

     使用方式(例如在 MainForm 中显示用户名)

    lblWelcome.Text = $"欢迎你,{UserContext.LoginUser.UserName}!";

    三层调用关系总结

    UI (LoginForm) –> BLL (AdminBLL.Login) –>DAL (AdminDAL.GetAdminByLoginId) –>DB (SQL Server – Admin 表)

            依赖方向:UI → BLL → DAL → Models

            数据流向:DB → DAL(构造 Model)→ BLL(验证)→ UI(展示/跳转)

            状态共享:通过 UserContext 静态类实现跨窗体用户信息共享

    好处:

  • 分层清晰:各层职责单一,便于测试和维护。
  • 安全防护:参数化查询防 SQL 注入。
  • 用户体验:SunnyUI 提供现代化登录界面 + 自动错误提示。
  • 全局状态:UserContext 避免了频繁传参,方便权限控制。
  • 可扩展性:未来可轻松加入“记住我”、“验证码”、“密码加密”等功能
  • 注意:

     ADO.NET 技术实现数据库操作的标准流程:

    查询:

  • 编写sql语句
  • 如果参数,添加参数
  • 调用DBHelper.GetReader() ===> SqlDataReader
  • SqlDataReader的Read() 提取每一行数据 –> 转换为Model对象 如果是多行, List, 单行–>Model
  • 返回数据
  • 增删改:

  • 编写sql语句
  • 如果参数,添加参数
  • 调用DBHelper.CUD) ===> int
  • 返回数据
  • 1. 查询操作逻辑

    • 核心是通过 DBHelper.GetReader() 获取 SqlDataReader 对象,再将查询结果转化为业务模型(Model):
      • 若返回多行数据,需将每行转为 Model 后存入 List 集合;
      • 若仅返回单行,直接转为单个 Model 对象。
    • 流程:编写SQL → 处理参数 → 执行查询获取数据读取器 → 解析数据为模型 → 返回数据。

    2. 增删改(CUD)操作逻辑

    • 核心是通过 DBHelper.CUD() 执行增删改操作,返回受影响的行数(int 类型)。
    • 流程:编写SQL → 处理参数 → 执行CUD操作获取影响行数 → 返回数据。

    关键差异

    • 查询 侧重“数据读取与模型转换”,需处理 SqlDataReader 的读取和模型映射;
    • 增删改 侧重“执行操作并获取影响行数”,直接通过 CUD() 方法返回执行结果(影响行数)。

    效果:

    输入admin数据库表中的用户的用户名和密码,即可跳转至主页面;

    图11

    输入表中对应的用户名和密码,点击登录按钮,登录成功跳转至主页面,并且底部显示了用户信息;用户名密码输入错误,也会弹框反馈给用户;点击取消按钮,退出程序;

    图12

    五、查询、添加、修改、删除模块

    1. 复杂查询:多表联查

    为了在列表中同时显示学生、班级和状态信息,必须进行 三表联查 (Student JOIN Class JOIN UserState)。

            模型设计:StudentModel 中包含 ClassModel 和 UserStateModel 两个导航属性,实现对象关系映射(ORM 的简易版)。

    SQL 示例:

    SELECT s.*, us.UserState, c.ClassName, c.GradeID
    FROM Student s
    JOIN UserState us ON s.UserStateId = us.UserStateId
    JOIN Class c ON s.ClassId = c.ClassId

    2. 性能优化:分页查询(Pagination)

    面对大量数据,一次性加载所有记录是不可行的。解决方案是 服务端分页。

    • 核心思想:每次只从数据库获取当前页所需的数据。
    • 关键技术:使用 SQL Server 的 OFFSET … FETCH NEXT 语法。
    • 分页模型:创建通用的 PageModel<T> 封装分页信息(当前页、页大小、总记录数、总页数、当前页数据)。
    • 流程:
    • DAL 层提供 SelectPage(pageIndex, pageSize) 方法执行分页 SQL。
    • BLL 层调用 DAL 获取数据,并计算总记录数(通过 Count() 方法),填充 PageModel。
    • UI 层接收 PageModel,绑定到 DataGridView 并更新分页控件。

    3. 高级功能:动态条件分页查询

    用户需要根据姓名、性别、班级、地址、状态等任意组合进行筛选。

    • 条件封装:创建 StudentCondition 类,将所有可能的查询条件集中管理。
    • 动态 SQL 拼接:在 DAL 层,根据 StudentCondition 对象的值,动态构建 WHERE 子句。
      • 使用 StringBuilder 拼接 SQL。
      • 使用 参数化查询 防止 SQL 注入。
      • 初始条件设为 WHERE 1=1,方便后续 AND 拼接。
    • UI 交互:
      • 班级和状态下拉框 (ComboBox) 的选项需要从数据库动态加载(通过 ClassBLL 和 UserStateBLL)。
      • “查询”按钮点击时,收集表单数据构造 StudentCondition 对象,并触发第一页的条件分页查询。
      • 分页控件的 PageChanged 事件也需要带上相同的 StudentCondition 对象,以保证翻页时条件不变。

    4. CRUD 全链路实现

            新增 (Create):表单验证(非空检查);构造 StudentModel 对象;调用 StudentBLL.AddStudent() → StudentDAL.Insert(),执行 INSERT 语句。        

    • 修改 (Update):
      • 数据传递:通过 EditStudentForm 的 Student 属性,将选中行的数据从列表页传递到编辑页。
      • 编辑页 Load 事件中,将 Student 对象的属性回填到各个控件。
      • 提交时,将修改后的数据组装成 StudentModel,调用 Update 方法执行 UPDATE … WHERE StudentId = @StudentId。
    • 删除 (Delete):
      • 在 DataGridView 的 CellContentClick 事件中捕获“删除”按钮点击。
      • 弹出确认对话框。
      • 调用 DeleteById 方法执行 DELETE 语句,并从 DataGridView 中移除对应行。

    5. UI/UX 细节处理

            窗体复用:通过 FormNavigator 工具类统一管理 MDI 子窗体的打开逻辑,避免重复代码,并确保同一类型窗体不被多次打开。

            数据绑定:使用 BindingSource 作为 DataGridView 的数据源,这是一种更优雅和强大的绑定方式。

            空值处理:在读取数据库字段时(如 Phone, Address),使用 dataReader.IsDBNull() 检查是否为 NULL,避免异常。

    总结:

  • 关注点分离 (SoC):UI 只负责展示和交互,BLL 处理业务规则,DAL 专注数据存取。
  • 防御性编程:表单提交前的非空验证、SQL 参数化、空值检查。
  • 可维护性:将复杂的 SQL 逻辑封装在 DAL,将 UI 导航逻辑封装在 FormNavigator。
  • 用户体验:分页、条件查询、操作确认、即时反馈(成功/失败提示)。
  • 效果展示:

    运行程序

    图13

    添加学生

    图14

    学生列表有我们刚刚添加的信息;

    图15

    再刷新数据库,看学生表是否新增了这条信息;

    图16

    可以看到数据库有我们的刚刚手动添加的学生信息,证明添加学生模块完成;

    查询模块:

    1.查询所有,与数据库信息同步,DataGridView里共10条记录,因为使用了分页查询,让每一页只显示1条信息;并且到了第一页就不能让用户点击上一页按钮,到了第10页就不能让用户点击下一页按钮,符合设计标准;

    图17

    2.带条件查询(可以单条件,也能多条件),实现条件筛选;

    单条件:

    图18

    多条件:

    本质是使用了模糊查询;

    图19

    查询用户苏洋的信息:

    图20

    修改苏洋的信息:

    点击DataGridView的编辑按钮,会显示用户的原本信息;

    图21

    我们修改我们要修改的信息,比如我把地址改为旧金山、登录密码改为1301、班级改为S2103;

    图22

    点击修改按钮,弹框显示修改成功,点击确定;

    图23

    这时候我们再点击查询,就查不到了,因为用户苏洋的相关信息已经被修改了;

    图24

    再返回数据库进行刷新,看看是否真正修改成功了;

    图25

    数据库信息已经被修改成功,说明ADO操作数据库成功;

    删除模块:点击删除按钮,删除用户即可

    六、总结

    传统开发陷阱MySchool 的实践方案价值提升
    UI 层硬编码数据库逻辑 三层架构分离(UI→BLL→DAL) 业务变更无需修改 UI
    一次性加载全量数据 服务端分页 + 动态条件查询 10万条数据流畅无卡顿
    密码明文存储 参数化 SQL + 安全设计 防止 SQL 注入(教学版)
    全局状态散落各处 UserContext 单例管理 跨窗体权限控制零成本

    当学生管理模块需要新增“状态筛选”时,只需在 StudentCondition 类中加一行属性,BLL 层自动适配,UI 层无需改动——这就是分层架构的终极价值。

    关键技术突破点

    1. 分页查询:从“卡顿”到“丝滑”的革命

    // DAL 层:动态生成分页 SQL(避免全表扫描)
    public PageModel<StudentModel> GetPage(int pageIndex, int pageSize, StudentCondition condition)
    {
    string baseSql = "SELECT s.*, c.ClassName, us.UserState FROM Student s …";
    string whereSql = BuildWhereCondition(condition); // 动态拼接 WHERE
    string countSql = $"SELECT COUNT(*) FROM Student {whereSql}";

    int total = DBHelper.GetScalar(countSql); // 获取总记录数
    string pageSql = $"{baseSql} {whereSql} ORDER BY StudentId OFFSET {pageIndex} ROWS FETCH NEXT {pageSize} ROWS ONLY";

    var list = new List<StudentModel>();
    using (var reader = DBHelper.GetReader(pageSql))
    {
    while (reader.Read())
    list.Add(ConvertToModel(reader));
    }

    return new PageModel<StudentModel>(total, list);
    }

    早期项目常因 SELECT * 一次性加载 10 万条数据导致卡死,而 服务端分页 让查询时间从 10s+ 降至 200ms。

    2.登录流程

    图26

    3. 全局状态管理:告别“传参地狱”

    // 任意窗体中获取当前用户
    public partial class MainForm : Form
    {
    public MainForm()
    {
    lblWelcome.Text = $"欢迎,{AdminContext.CurrentAdmin.UserName}!";
    }
    }

    // 登录成功后设置
    AdminContext.CurrentAdmin = admin; // 1 行代码,全局生效

    从“在 10 个窗体中传递 AdminModel” → “1 行代码全局访问”,开发效率提升 40%

    MySchool 项目的价值,不在于它实现了多少功能,而在于它用最朴素的 WinForm + ADO.NET 诠释了企业级开发的核心原则:

            分层(让变更成本可控)

            分页(让性能不成为瓶颈)

            状态管理(让代码逻辑清晰)

    这个项目还是有很多值得学习的地方,还需继续钻研…

    赞(0)
    未经允许不得转载:171主机测评 » WinForm 综合实战:基于三层架构的 MySchool 学校信息管理系统开发指南——登录模块及学生信息管理模块
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址