VB Visual Basic 2026 中文教程
第 36 课 · 36 课最终综合项目

综合项目:桌面管理系统

这是整个 Visual Basic 2026 中文教程的最终综合项目。我们不再学习单一语法,而是把前面已经掌握的 Windows Forms、对象设计、Repository、Service、参数化 SQL、DataGridView、搜索筛选、LINQ、Async/Await、JSON、Web API、应用配置、用户偏好和发布部署组合起来,完成一个结构清楚、可继续扩展的学生桌面管理系统。

环境:Visual Studio 2026 · VB.NET · .NET 10数据库:StudentManagementDB最终项目:StudentDesktopManagementSystem2026

本课完成目标

  • 规划一个完整 Windows Forms 桌面管理系统。
  • 使用多窗体设计 Dashboard、Students、Settings 与 API 工具。
  • 使用 Student 实体类表示领域数据。
  • 通过 IStudentRepository 隔离数据访问。
  • 使用 StudentRepository 执行参数化 SQL CRUD。
  • 使用 StudentService 集中业务验证。
  • 使用 Async/Await 异步读取数据库。
  • 使用 DataGridView 显示、选择与刷新记录。
  • 使用 SQL 与 LINQ 完成搜索、筛选与统计。
  • 使用 appsettings.json 和用户偏好配置。
  • 加入 JSON / Web API 工具作为扩展功能。
  • 完成 Release 与 Publish 前最终检查。

36.1 最终项目要解决什么问题?

StudentDesktopManagementSystem2026 是一个小型但结构完整的学生资料管理系统。

Dashboard

总览

显示学生总数、平均成绩、最高成绩与及格人数。

Students

学生管理

新增、读取、修改、删除、搜索、筛选和排序。

Settings

用户设置

记住课程、窗体大小和应用偏好。

API Tool

网络资料

调用公开 API,展示 JSON 与 HttpClient 整合。

Architecture

分层结构

UI、Service、Repository、Database 各负其责。

Deployment

可发布

配置文件、用户资料与外部数据库依赖都有明确处理。

36.2 系统整体架构

Windows FormsMainForm / StudentForm
Services业务规则
Contracts接口
DataRepository
SQL ServerStudentManagementDB

重要原则:

Form 不直接写 SQL
Service 不直接读取 TextBox
Repository 不弹 MessageBox
Student 不负责数据库连接

36.3 项目结构

StudentDesktopManagementSystem2026 ├─ Models │ ├─ Student.vb │ ├─ UserPreferences.vb │ └─ ApiUser.vb ├─ Contracts │ └─ IStudentRepository.vb ├─ Data │ ├─ DatabaseHelper.vb │ └─ StudentRepository.vb ├─ Services │ ├─ StudentService.vb │ ├─ SettingsService.vb │ └─ ApiUserService.vb ├─ Configuration │ └─ AppConfiguration.vb ├─ Forms │ ├─ MainForm.vb │ ├─ StudentForm.vb │ ├─ SettingsForm.vb │ └─ ApiViewerForm.vb └─ appsettings.json

36.4 数据库结构

继续使用前面建立的:

StudentManagementDB
dbo.Students

如果需要重新建立:

CREATE TABLE dbo.Students
(
    StudentID INT IDENTITY(1,1) PRIMARY KEY,
    Name NVARCHAR(100) NOT NULL,
    Course NVARCHAR(100) NOT NULL,
    Score DECIMAL(5,2) NOT NULL
        CHECK (Score BETWEEN 0 AND 100)
);

36.5 Student.vb

Public Class Student

    Public Property StudentID As Integer

    Public Property Name As String = ""

    Public Property Course As String = ""

    Public Property Score As Decimal

    Public ReadOnly Property IsPass As Boolean
        Get
            Return Score >= 50D
        End Get
    End Property

End Class

这里加入了一个只读计算属性:

IsPass

它属于 Student 本身可以表达的状态,不需要数据库字段。

36.6 IStudentRepository.vb

Public Interface IStudentRepository

    Function GetAllAsync() _
        As Task(Of List(Of Student))

    Function InsertAsync(
        student As Student) _
        As Task(Of Integer)

    Function UpdateAsync(
        student As Student) _
        As Task(Of Integer)

    Function DeleteAsync(
        studentId As Integer) _
        As Task(Of Integer)

    Function SearchAsync(
        keyword As String,
        course As String) _
        As Task(Of List(Of Student))

End Interface

36.7 StudentRepository:数据库实现

文件顶部:

Imports Microsoft.Data.SqlClient

类声明:

Public Class StudentRepository
    Implements IStudentRepository

36.8 异步读取全部学生

Public Async Function GetAllAsync() _
    As Task(Of List(Of Student)) _
    Implements IStudentRepository.GetAllAsync

    Const sql As String =
        "SELECT StudentID, Name, Course, Score " &
        "FROM dbo.Students " &
        "ORDER BY StudentID;"

    Dim students As New List(Of Student)()

    Using connection As New SqlConnection(
        DatabaseHelper.ConnectionString)

        Using command As New SqlCommand(
            sql,
            connection)

            Await connection.OpenAsync()

            Using reader As SqlDataReader =
                Await command.ExecuteReaderAsync()

                While Await reader.ReadAsync()

                    students.Add(
                        New Student With {
                            .StudentID = reader.GetInt32(0),
                            .Name = reader.GetString(1),
                            .Course = reader.GetString(2),
                            .Score = reader.GetDecimal(3)
                        })

                End While

            End Using

        End Using

    End Using

    Return students

End Function

36.9 参数化异步 INSERT

Public Async Function InsertAsync(
    student As Student) _
    As Task(Of Integer) _
    Implements IStudentRepository.InsertAsync

    Const sql As String =
        "INSERT INTO dbo.Students " &
        "(Name, Course, Score) " &
        "VALUES (@Name, @Course, @Score);"

    Using connection As New SqlConnection(
        DatabaseHelper.ConnectionString)

        Using command As New SqlCommand(
            sql,
            connection)

            command.Parameters.Add(
                "@Name",
                SqlDbType.NVarChar,
                100).Value =
                student.Name

            command.Parameters.Add(
                "@Course",
                SqlDbType.NVarChar,
                100).Value =
                student.Course

            Dim scoreParameter As SqlParameter =
                command.Parameters.Add(
                    "@Score",
                    SqlDbType.Decimal)

            scoreParameter.Precision = 5
            scoreParameter.Scale = 2
            scoreParameter.Value =
                student.Score

            Await connection.OpenAsync()

            Return Await command.ExecuteNonQueryAsync()

        End Using

    End Using

End Function

36.10 UPDATE 与 DELETE

Public Async Function UpdateAsync(
    student As Student) _
    As Task(Of Integer) _
    Implements IStudentRepository.UpdateAsync

    Const sql As String =
        "UPDATE dbo.Students " &
        "SET Name = @Name, " &
        "Course = @Course, " &
        "Score = @Score " &
        "WHERE StudentID = @StudentID;"

    Using connection As New SqlConnection(
        DatabaseHelper.ConnectionString)

        Using command As New SqlCommand(
            sql,
            connection)

            command.Parameters.Add(
                "@StudentID",
                SqlDbType.Int).Value =
                student.StudentID

            command.Parameters.Add(
                "@Name",
                SqlDbType.NVarChar,
                100).Value =
                student.Name

            command.Parameters.Add(
                "@Course",
                SqlDbType.NVarChar,
                100).Value =
                student.Course

            Dim scoreParameter As SqlParameter =
                command.Parameters.Add(
                    "@Score",
                    SqlDbType.Decimal)

            scoreParameter.Precision = 5
            scoreParameter.Scale = 2
            scoreParameter.Value =
                student.Score

            Await connection.OpenAsync()

            Return Await command.ExecuteNonQueryAsync()

        End Using

    End Using

End Function
Public Async Function DeleteAsync(
    studentId As Integer) _
    As Task(Of Integer) _
    Implements IStudentRepository.DeleteAsync

    Const sql As String =
        "DELETE FROM dbo.Students " &
        "WHERE StudentID = @StudentID;"

    Using connection As New SqlConnection(
        DatabaseHelper.ConnectionString)

        Using command As New SqlCommand(
            sql,
            connection)

            command.Parameters.Add(
                "@StudentID",
                SqlDbType.Int).Value =
                studentId

            Await connection.OpenAsync()

            Return Await command.ExecuteNonQueryAsync()

        End Using

    End Using

End Function

综合姓名关键字与课程:

Public Async Function SearchAsync(
    keyword As String,
    course As String) _
    As Task(Of List(Of Student)) _
    Implements IStudentRepository.SearchAsync

    Dim sql As String =
        "SELECT StudentID, Name, Course, Score " &
        "FROM dbo.Students " &
        "WHERE Name LIKE @NamePattern "

    If course <> "全部课程" Then
        sql &=
            "AND Course = @Course "
    End If

    sql &=
        "ORDER BY Name;"

    Dim students As New List(Of Student)()

    Using connection As New SqlConnection(
        DatabaseHelper.ConnectionString)

        Using command As New SqlCommand(
            sql,
            connection)

            command.Parameters.Add(
                "@NamePattern",
                SqlDbType.NVarChar,
                100).Value =
                "%" & keyword & "%"

            If course <> "全部课程" Then

                command.Parameters.Add(
                    "@Course",
                    SqlDbType.NVarChar,
                    100).Value =
                    course

            End If

            Await connection.OpenAsync()

            Using reader As SqlDataReader =
                Await command.ExecuteReaderAsync()

                While Await reader.ReadAsync()

                    students.Add(
                        New Student With {
                            .StudentID = reader.GetInt32(0),
                            .Name = reader.GetString(1),
                            .Course = reader.GetString(2),
                            .Score = reader.GetDecimal(3)
                        })

                End While

            End Using

        End Using

    End Using

    Return students

End Function

End Class

36.12 StudentService:业务规则

Public Class StudentService

    Private ReadOnly repository As IStudentRepository

    Public Sub New(
        repository As IStudentRepository)

        Me.repository =
            repository

    End Sub

    Public Function Validate(
        student As Student,
        ByRef errorMessage As String) As Boolean

        If String.IsNullOrWhiteSpace(
            student.Name) Then

            errorMessage =
                "请输入学生姓名。"

            Return False

        End If

        If String.IsNullOrWhiteSpace(
            student.Course) Then

            errorMessage =
                "请输入课程名称。"

            Return False

        End If

        If student.Score < 0D OrElse
           student.Score > 100D Then

            errorMessage =
                "成绩必须介于 0 与 100。"

            Return False

        End If

        errorMessage = ""
        Return True

    End Function

36.13 Service 异步 CRUD

Public Async Function GetStudentsAsync() _
    As Task(Of List(Of Student))

    Return Await repository.GetAllAsync()

End Function

Public Async Function AddStudentAsync(
    student As Student,
    errorMessage As Action(Of String)) _
    As Task(Of Boolean)

    Dim message As String = ""

    If Not Validate(
        student,
        message) Then

        errorMessage(message)
        Return False

    End If

    Return Await repository.InsertAsync(
        student) = 1

End Function

为了让本项目更容易理解,也可以使用上一课熟悉的 ByRef errorMessage 版本。这里展示的是另一种把错误回传给调用者的方法。

36.14 MainForm:系统主界面

MainForm 建议使用:

MenuStrip
ToolStrip
StatusStrip
Panel
DataGridView
Label Dashboard Cards

菜单:

File
├─ Refresh
└─ Exit

Students
├─ Add Student
├─ Edit Selected
└─ Delete Selected

Tools
├─ Settings
└─ API Viewer

Help
└─ About
Student Desktop Management System 2026
128Students
79.42Average Score
98.00Highest Score
113Passed
Add Student Edit Delete Refresh Settings
IDNameCourseScorePass
1李明Visual Basic 202688.00True
4李华Visual Basic 202693.00True
5张敏C# Programming67.00True

36.15 MainForm 建立依赖

Public Class MainForm

    Private ReadOnly studentService As StudentService

    Private students As New List(Of Student)()

    Public Sub New()

        InitializeComponent()

        Dim repository As IStudentRepository =
            New StudentRepository()

        studentService =
            New StudentService(
                repository)

    End Sub

36.16 MainForm 异步加载

Private Async Sub MainForm_Load(
    sender As Object,
    e As EventArgs) Handles MyBase.Load

    ConfigureGrid()

    Await RefreshStudentsAsync()

End Sub

36.17 RefreshStudentsAsync()

Private Async Function RefreshStudentsAsync() As Task

    UseWaitCursor =
        True

    lblStatus.Text =
        "正在读取学生资料..."

    Try

        students =
            Await studentService.GetStudentsAsync()

        dgvStudents.DataSource =
            Nothing

        dgvStudents.DataSource =
            students

        UpdateDashboard()

        lblStatus.Text =
            $"已加载 {students.Count} 笔记录。"

    Catch ex As Exception

        MessageBox.Show(
            ex.Message,
            "读取失败",
            MessageBoxButtons.OK,
            MessageBoxIcon.Error)

        lblStatus.Text =
            "读取失败。"

    Finally

        UseWaitCursor =
            False

    End Try

End Function

36.18 用 LINQ 更新 Dashboard

Private Sub UpdateDashboard()

    lblTotalStudents.Text =
        students.Count.ToString()

    If students.Count = 0 Then

        lblAverageScore.Text =
            "0.00"

        lblHighestScore.Text =
            "0.00"

        lblPassCount.Text =
            "0"

        Exit Sub

    End If

    lblAverageScore.Text =
        students.
            Average(
                Function(s) s.Score).
            ToString("N2")

    lblHighestScore.Text =
        students.
            Max(
                Function(s) s.Score).
            ToString("N2")

    lblPassCount.Text =
        students.
            Count(
                Function(s) s.IsPass).
            ToString()

End Sub

36.19 StudentForm:新增与修改共用一个窗体

StudentForm 控件:

txtStudentID
txtName
cmbCourse
txtScore
btnSave
btnCancel

公开结果:

Public ReadOnly Property StudentResult As Student

36.20 StudentForm 核心代码

Public Class StudentForm

    Public ReadOnly Property StudentResult As Student

    Public Sub New(
        student As Student)

        InitializeComponent()

        If student IsNot Nothing Then

            txtStudentID.Text =
                student.StudentID.ToString()

            txtName.Text =
                student.Name

            cmbCourse.Text =
                student.Course

            txtScore.Text =
                student.Score.ToString()

        End If

    End Sub

    Private Sub btnSave_Click(
        sender As Object,
        e As EventArgs) Handles btnSave.Click

        Dim score As Decimal

        If Not Decimal.TryParse(
            txtScore.Text,
            score) Then

            MessageBox.Show(
                "请输入有效成绩。")

            Exit Sub

        End If

        Dim studentId As Integer

        Integer.TryParse(
            txtStudentID.Text,
            studentId)

        StudentResult =
            New Student With {
                .StudentID = studentId,
                .Name = txtName.Text.Trim(),
                .Course = cmbCourse.Text.Trim(),
                .Score = score
            }

        DialogResult =
            DialogResult.OK

    End Sub

End Class

36.21 新增学生

Private Async Sub mnuAddStudent_Click(
    sender As Object,
    e As EventArgs) Handles mnuAddStudent.Click

    Using form As New StudentForm(
        Nothing)

        If form.ShowDialog(Me) <>
           DialogResult.OK Then

            Exit Sub

        End If

        Dim errorText As String = ""

        If Not studentService.Validate(
            form.StudentResult,
            errorText) Then

            MessageBox.Show(
                errorText)

            Exit Sub

        End If

        Await studentService.AddStudentAsync(
            form.StudentResult,
            Sub(message)
                errorText = message
            End Sub)

        Await RefreshStudentsAsync()

    End Using

End Sub

36.22 取得 DataGridView 当前学生

Private Function GetSelectedStudent() As Student

    Return TryCast(
        dgvStudents.CurrentRow?.
            DataBoundItem,
        Student)

End Function

36.23 修改与删除

修改时:

Dim selected As Student =
    GetSelectedStudent()

If selected Is Nothing Then

    MessageBox.Show(
        "请先选择学生。")

    Exit Sub

End If

然后打开 StudentForm,并把修改后的 Student 交给 Service。

删除时:

Dim selected As Student =
    GetSelectedStudent()

If selected Is Nothing Then
    Exit Sub
End If

Dim result =
    MessageBox.Show(
        $"确定删除 {selected.Name}?",
        "确认删除",
        MessageBoxButtons.YesNo,
        MessageBoxIcon.Warning)

If result <> DialogResult.Yes Then
    Exit Sub
End If

36.24 搜索与筛选

MainForm 加入:

txtSearch
cmbFilterCourse
btnSearch
btnShowAll

搜索按钮:

Private Async Sub btnSearch_Click(
    sender As Object,
    e As EventArgs) Handles btnSearch.Click

    Dim repository As IStudentRepository =
        New StudentRepository()

    Dim results As List(Of Student) =
        Await repository.SearchAsync(
            txtSearch.Text.Trim(),
            cmbFilterCourse.Text)

    dgvStudents.DataSource =
        Nothing

    dgvStudents.DataSource =
        results

    lblStatus.Text =
        $"搜索结果:{results.Count} 笔。"

End Sub
还能进一步改善 更严格的分层设计应该把 SearchAsync 也通过 StudentService 暴露给 Form,而不是让 Form 重新建立 Repository。这里把它作为重构练习留给学生。

36.25 整合 Lesson 34 设置功能

appsettings.json:

{
  "Application": {
    "Name": "Student Desktop Management System 2026",
    "DefaultCourse": "Visual Basic 2026"
  },
  "Api": {
    "BaseUrl": "https://jsonplaceholder.typicode.com"
  }
}

MainForm 标题:

Me.Text =
    AppConfiguration.ApplicationName

SettingsForm 可以保存:

RememberLastCourse
LastCourse
WindowWidth
WindowHeight

36.26 整合 JSON 与 Web API

ApiViewerForm 使用 Lesson 33 的 ApiUser:

Public Class ApiUser

    Public Property Id As Integer
    Public Property Name As String
    Public Property Username As String
    Public Property Email As String

End Class

ApiUserService:

Imports System.Net.Http.Json

Public Class ApiUserService

    Private ReadOnly httpClient As New HttpClient()

    Public Async Function GetUsersAsync() _
        As Task(Of List(Of ApiUser))

        Dim url As String =
            AppConfiguration.ApiBaseUrl &
            "/users"

        Dim users =
            Await httpClient.GetFromJsonAsync(
                Of List(Of ApiUser))(
                    url)

        Return If(
            users,
            New List(Of ApiUser)())

    End Function

End Class

36.27 多窗体协作

MainForm
系统入口、Dashboard、学生列表与导航。
StudentForm
新增和修改学生。
SettingsForm
用户偏好。
ApiViewerForm
Web API / JSON 演示。

打开 Settings:

Using form As New SettingsForm()

    form.ShowDialog(Me)

End Using

36.28 统一错误处理原则

Repository

让数据库异常继续往上抛,不直接 MessageBox。

Form

在用户操作边界 Catch,并显示容易理解的错误信息。

例如:

Try

    Await RefreshStudentsAsync()

Catch ex As Exception

    MessageBox.Show(
        ex.Message)

End Try

36.29 用户实际操作流程

1
启动应用

读取 appsettings.json 与用户偏好。

2
异步加载学生

Repository → Service → MainForm。

3
Dashboard

LINQ 计算总数、平均、最高分和及格人数。

4
CRUD

StudentForm + Service + 参数化 SQL。

5
搜索

LIKE + Course 参数筛选。

6
设置 / API

JSON 保存偏好并调用远程 API。

36.30 建议按阶段完成,不要一次写完

Milestone 1 · 数据层

Student、IStudentRepository、StudentRepository 编译成功。

Milestone 2 · Service

验证与异步 CRUD 完成。

Milestone 3 · MainForm

DataGridView 和 Dashboard 正常。

Milestone 4 · StudentForm

新增、修改、删除完整。

Milestone 5 · 搜索 / 设置

筛选、配置与用户偏好完成。

Milestone 6 · API / Publish

远程 API 与发布测试完成。

36.31 最终测试清单

✓ 数据库连接正常。
✓ 第一次启动可读取学生。
✓ 新增后 DataGridView 自动刷新。
✓ 修改正确 StudentID。
✓ 删除有确认对话框。
✓ 搜索使用参数化 SQL。
✓ 空数据时 Dashboard 不发生 Average 异常。
✓ API 断网时有错误提示。
✓ 用户设置可以保存和恢复。
✓ appsettings.json 正确复制到输出目录。
✓ Release Rebuild 为 0 Errors。
✓ Publish 目录可以独立运行。

36.32 发布最终项目

完成后:

Configuration = Release
Target = Folder
Deployment Mode = Self-contained
Target Runtime = win-x64

然后测试:

publish
└─ StudentDesktopManagementSystem2026.exe
仍然要检查 LocalDB 如果最终项目继续使用 (localdb)\MSSQLLocalDB,目标电脑仍需要相应 LocalDB 环境和 StudentManagementDB。Self-contained 只解决 .NET Runtime,不会自动部署数据库服务器。

36.33 GitHub 项目建议

建议仓库包含:

src\
database\
  create-database.sql
README.md
.gitignore

README 可以说明:

项目功能
运行环境
数据库创建步骤
NuGet 包
配置方式
截图
发布说明
不要提交真正 API Key、密码和私密连接字符串 如果未来加入需要秘密凭证的 API,把秘密放到环境变量或安全部署配置中。

36.34 完成后可以怎样继续扩展?

Courses

课程管理

建立 Courses 表与 CourseRepository。

Reports

报表导出

CSV、JSON、HTML 或 PDF 报表。

Authentication

用户登入

管理员与普通用户权限。

Charts

统计图表

课程人数、成绩分布与趋势。

Web API

服务化

把数据库访问移到 ASP.NET Core Web API。

AI

AI 辅助功能

加入自然语言查询、摘要或学生表现分析。

36.35 综合项目常见问题

问题 1

一开始就写所有功能

综合项目应该按 Milestone 完成,每一步都先 Rebuild 和测试。

问题 2

Form1 又开始塞满 SQL

最终项目仍要坚持 Repository / Service 的职责分离。

问题 3

异步方法调用忘记 Await

数据库与 API 的异步操作都要保持正确的 Async/Await 调用链。

问题 4

只测试成功路径

还要测试无数据、错误输入、断网、数据库不可用和找不到配置文件。

36.36 最终挑战

  1. 把 SearchAsync 从 MainForm 的 Repository 调用移到 StudentService。
  2. 加入 CourseRepository 与课程数据库表。
  3. 建立 StudentDetailsForm,只读显示完整学生资料。
  4. 加入“Top 5 Students” LINQ 面板。
  5. 加入 CSV 或 JSON Export 功能。
  6. 把 API Viewer 改成从真实课程 API 读取资料。
  7. 建立 Self-contained win-x64 发布版本,并在另一台电脑测试。

最终复习

  1. 为什么 Form 不应该直接写 SQL?
  2. IStudentRepository 带来什么好处?
  3. StudentService 应该负责哪些事情?
  4. 参数化 SQL 为什么必须保留到最终项目?
  5. 为什么数据库读取适合使用 Async/Await?
  6. LINQ 在 Dashboard 中解决了哪些统计问题?
  7. appsettings.json 与 user-settings.json 有什么不同?
  8. Web API 为什么通常使用 JSON?
  9. 为什么 Self-contained 仍然不代表 LocalDB 已部署?
  10. 综合项目为什么应该按 Milestone 分阶段完成?

恭喜完成 36 课 Visual Basic 2026 中文教程

从第 1 课认识 Visual Basic 与 Windows Forms,到第 36 课完成一个分层、异步、数据库驱动并能够发布的桌面管理系统,你已经走过了变量、判断、循环、方法、类、集合、文件、资源、多窗体、数据库、CRUD、LINQ、接口、多态、Async/Await、JSON、Web API、配置与部署的完整学习路径。

下一步最重要的不是再背更多语法,而是继续扩展这个最终项目:增加真实功能、重构重复代码、编写测试、部署给别人使用,并从实际问题中继续学习。