ASP.NET MVC学习笔记|代码与日常的交织

Doc Map
  1. 一点开场
    1. 课堂上的小插曲
  2. MVC 与 MVVM 的第一印象
  3. LINQ 的世界
  4. Lambda 表达式与扩展方法
    1. 自定义扩展方法示例
    2. 又一次走神
  5. 学习 MVC 的小结
  6. 写在最后

一点开场

写这篇文章的时候,我其实心情挺复杂的。上个学期学过 ASP.NET 的基础知识,那时候更多是“了解”,也就是会写点 C# 的 WebForm,能连个数据库,能写个小表单。但这学期开始,老师说我们要系统学习 ASP.NET MVC架构,我突然觉得“噢,这下要上正菜了”。

ASP.NET 教材

MVC 这个概念,之前也听过,只是模模糊糊知道它是“分层、解耦、让代码更清晰”的一种模式。而这次一上来,老师就顺带抛出一个更“文艺”的词:MVVM(Model - View - ViewModel)。当时我愣了半天,心想:我是不是走错片场了?明明是 MVC 课,结果学的却是 MVVM?

不过也挺好。带着一点懵,反而让我更认真地去琢磨:为什么要分这些层?为什么写代码的时候要有这么多角色?是不是光一个控制器就能解决问题?

答案很简单:当你项目越写越大,你就会知道“结构”有多重要。

课堂上的小插曲

当然,所谓“认真学习”,也得打个引号。因为我上课时经常会——跑神。 比如老师在讲 LINQ 的时候,我打开 Visual Studio,开始写一个查询:

var result = from s in students
             where s.Score > 80
             select s.Name;

写到一半,我突然想: “欸,中午要不要去楼下吃个炒饭?” 🤔 “要不要把昨天那个 C# 小工具优化一下?” “还有那首歌的旋律怎么一直在脑子里绕来绕去?”

等我回过神来,光标还在闪,代码还没写完,我完全忘了自己要实现的逻辑……只能尴尬地傻笑。

这种事发生得太频繁了,甚至让我养成了一个习惯:写代码前最好先写个草稿,把逻辑写在纸上。这样即使走神了,回来一看纸上的流程图,也能重新找回轨道。要不然,代码写着写着就变成“散文诗”了。

MVC 与 MVVM 的第一印象

既然是学习笔记,那还是得回到主题。

MVC:

  • Model: 模型层,管数据和业务逻辑。
  • View:视图层,管页面展示。
  • Controller:控制器,负责调度、把 Model 和 View 联系起来。

而 MVVM 在此基础上,多了一个 ViewModel,本质上就是让数据和视图可以双向绑定,更适合那种交互很多的场景。

我一开始不太能理解,后来写了几个小例子才明白:

  • 如果用 MVC,我要在控制器里拼命写逻辑,最后再传数据给视图。
  • 如果用 MVVM,我直接让 ViewModel 帮我把数据和页面绑定起来,省事。

就像炒菜一样,MVC 是“你要自己切菜、炒菜、上桌”,MVVM 是“有人帮你切好菜,甚至把盘子都端上来了”,你只管享用。

LINQ 的世界

接着讲讲 LINQ。这个东西我在学数据库和 C# 的时候就接触过。并且,之前使用 WPF 桌面应用程序开发扫雷程序时,使用过数据库,所以对此也不算太陌生,只是这次算是“系统复习”。

LINQ(Language Integrated Query)让我感受到 C# 的优雅。(哈哈哈哈,其实这个优雅最开始学习 C# 时,课本上就将其作为一个特点描述,刚开始很疑惑,但是 C# 代码写多了之后就知道为什么了,尤其是写 LINQ)它不像传统 SQL 那样生硬,反而是把查询写进了语言本身,就像写英文句子一样自然。

示例:

  • 查询学生分数大于 80 的人
    var result = from s in students
             where s.Score > 80
             select s.Name;
  • 排序
    var result = from s in students
             orderby s.Score descending
             select s;
  • 分组
    var result = from s in students
             group s by s.Class into g
             select new { Class = g.Key, Count = g.Count() };

这些语法在第一次见时,会觉得很神奇:“原来 C# 还能这样查数据?”

后来我才发现,这就是抽象的力量。LINQ 让我们不必关心底层是数据库、集合还是 XML 文件,只要写一样的语法,就能查数据。

不过说实话,刚开始写的时候,我经常被 Lambda 表达式绕晕。

Lambda 表达式与扩展方法

Lambda 的写法简直就是“箭头流”。一开始看着很懵:

students.Where(s => s.Score > 80).Select(s => s.Name);

其实意思和 LINQ 查询语法差不多,但写法更紧凑。 老师说:“这个就是函数式编程的一种体现。” 我当时在心里默默吐槽:“函数式、面向对象、事件驱动……感觉编程就是个名词收集器。”

不过慢慢写多了,发现 Lambda 真的很好用,特别是配合扩展方法。

自定义扩展方法示例
public static class StringExtensions
{
    public static bool IsLongerThan(this string str, int length)
    {
        return str.Length > length;
    }
}

// 使用
string text = "Hello World";
bool result = text.IsLongerThan(5);  // true

这种写法让我很有成就感:仿佛自己在给 .NET 库“偷偷加功能”。

又一次走神

写到扩展方法的时候,我又想到一个趣事。 有次写扩展方法,我把方法名起得特别奇怪,比如:

text.IsSuperDuperAwesomeLongerThan(10);

写完自己忍不住笑出声,结果同桌投来一个“你在干嘛”的眼神。 那一瞬间我才意识到,编程不仅仅是技术,还是一种“表达欲”。你写的代码,其实就是你在和未来的自己对话。

有时候,我会故意在注释里写点无关紧要的废话,(其实经常在网络上刷到过关于程序员为何写这么多“废话”,哈哈哈哈,现在知道了)比如:

// 这行代码可能以后会救你一命(别删!!!)

结果等到几周后真的看不懂自己写的逻辑时,那句注释还真成了救命稻草。

学习 MVC 的小结

总的来说,这一周的学习让我慢慢体会到 MVC 的美妙:

  • 它让代码更清晰,不会一团乱麻。
  • 它让逻辑和展示分开,不至于一个文件里堆满了 SQL、HTML、C#。
  • 它让我明白,软件架构其实就是“把复杂的事拆开,让人脑能看懂”。

而在学习的过程中,走神、跑偏、甚至写出奇怪代码,反而成了让我记忆更深的点。也许这就是所谓的“日常与学习的交织”。

写在最后

写完这篇笔记,其实我也没觉得自己把 MVC 学得多透彻,更多的是一种“边走边看”的感觉。就像上课的时候,我常常写着写着代码就发呆,想着午饭要吃啥🍜,想着昨天的 bug 还没改,想着生活里的乱七八糟。等回过神来,屏幕上的光标还在闪,我才发现:噢,原来学习也不一定要很正经,它可以夹杂着一点小走神、一点碎碎念。

所以这篇文章,算不上什么专业的教程,更像是我的一份学习日记。希望几年后再回头看,还能记得自己当初一边敲代码、一边开小差的样子。那应该会挺好笑,挺怀念的吧。


附录:SQL 数据库内容

/* ===== 0) 可选:创建并切换到数据库 ===== */
IF DB_ID(N'Demo') IS NULL
    CREATE DATABASE Demo;
GO
USE Demo;
GO

/* 为了可重复执行,先安全删除旧表(有外键的顺序要注意) */
IF OBJECT_ID(N'dbo.sc', N'U') IS NOT NULL DROP TABLE dbo.sc;
IF OBJECT_ID(N'dbo.course', N'U') IS NOT NULL DROP TABLE dbo.course;
IF OBJECT_ID(N'dbo.student', N'U') IS NOT NULL DROP TABLE dbo.student;
GO

/* ===== 1) student 表 =====
   字段:sno(PK), sname, sex, age, dept  */
CREATE TABLE dbo.student
(
    sno   INT           NOT NULL,               -- 学号
    sname NVARCHAR(20)  NOT NULL,               -- 姓名(支持中文)
    sex   NCHAR(1)      NULL,                   -- 性别:可存 '男' / '女'
    age   TINYINT       NULL,                   -- 年龄:0~255;见下方 CHECK
    dept  NVARCHAR(20)  NULL,                   -- 部门/院系
    CONSTRAINT PK_student PRIMARY KEY (sno),
    CONSTRAINT CK_student_age CHECK (age IS NULL OR age BETWEEN 0 AND 120),
    CONSTRAINT CK_student_sex CHECK (sex IS NULL OR sex IN (N'男', N'女'))
);
GO

/* ===== 2) course 表 =====
   字段:cno(PK), cname, tname, credit  */
CREATE TABLE dbo.course
(
    cno     INT           NOT NULL,             -- 课程号
    cname   NVARCHAR(20)  NOT NULL,             -- 课程名
    tname   NVARCHAR(20)  NOT NULL,             -- 教师姓名
    credit  TINYINT       NOT NULL,             -- 学分(无符号整数语义)
    CONSTRAINT PK_course PRIMARY KEY (cno),
    CONSTRAINT CK_course_credit CHECK (credit BETWEEN 0 AND 30)  -- 视校规调整
);
GO

/* ===== 3) sc 表(选课/成绩)=====
   字段:sno(FK→student), cno(FK→course), grade
   常见做法:以 (sno, cno) 作为复合主键 */
CREATE TABLE dbo.sc
(
    sno   INT NOT NULL,                         -- 学号 → student.sno
    cno   INT NOT NULL,                         -- 课号 → course.cno
    grade INT NOT NULL,                         -- 成绩(如需小数可改 DECIMAL(5,2))
    CONSTRAINT PK_sc PRIMARY KEY (sno, cno),
    CONSTRAINT FK_sc_student FOREIGN KEY (sno) REFERENCES dbo.student(sno)
        ON UPDATE NO ACTION ON DELETE CASCADE,  -- 学生删除时级联删除选课记录(按需)
    CONSTRAINT FK_sc_course  FOREIGN KEY (cno) REFERENCES dbo.course(cno)
        ON UPDATE NO ACTION ON DELETE CASCADE,
    CONSTRAINT CK_sc_grade CHECK (grade BETWEEN 0 AND 100) -- 视评分制调整
);
GO

INSERT dbo.student(sno, sname, sex, age, dept) VALUES
(10001, N'张三', N'男', 19, N'计算机系'),
(10002, N'李四', N'女', 20, N'软件学院');

INSERT dbo.course(cno, cname, tname, credit) VALUES
(1, N'数据库', N'王老师', 3),
(2, N'操作系统', N'赵老师', 4);

INSERT dbo.sc(sno, cno, grade) VALUES
(10001, 1, 86),
(10001, 2, 90),
(10002, 1, 88);

/* ===== 4)(可选)示例数据,验证外键与约束 ===== */

/* 学生表 */
INSERT dbo.student(sno, sname, sex, age, dept) VALUES
(10003, N'王五', N'男', 21, N'计算机系'),
(10004, N'赵六', N'女', 18, N'信息管理系'),
(10005, N'钱七', N'男', 22, N'软件学院'),
(10006, N'孙八', N'女', 20, N'通信工程系');

/* 课程表 */
INSERT dbo.course(cno, cname, tname, credit) VALUES
(3, N'数据结构', N'刘老师', 3),
(4, N'C语言程序设计', N'陈老师', 4),
(5, N'计算机网络', N'周老师', 3);

/* 选课表 */
INSERT dbo.sc(sno, cno, grade) VALUES
(10003, 3, 75),
(10003, 4, 82),
(10004, 2, 91),
(10004, 5, 87),
(10005, 1, 79),
(10005, 3, 85),
(10006, 4, 92),
(10006, 5, 88);

实训报告代码

下面的代码来自《基于 LINQ 数据模型的学生管理系统》实验报告,主要功能包括学生的增删改查。和课堂笔记里的内容互相呼应,算是一次课后的完整实战记录。

#region 按性别查询
string xingbie = "女";
var stu3 = dataClasses1DataContext1.students
    .Where(s => s.sex == xingbie)
    .Select(s => s.sname.ToUpper());

Console.WriteLine("女学生:");
foreach (var e in stu3)
{
    Console.WriteLine(e);
}
#endregion


#region 按年龄查询
int nianling = 20;
var stu2 = dataClasses1DataContext1.students
    .Where(s => s.age >= nianling)
    .Select(s => s.sname.ToUpper());

Console.WriteLine("年龄大于20岁的学生:");
foreach (var e in stu2)
{
    Console.WriteLine(e);
}
#endregion


#region 插入学生
student stu = new student
{
    sno = 10007,
    sname = "小明",
    sex = "男",
    age = 19,
    dept = "软件学院"
};
dataClasses1DataContext1.students.InsertOnSubmit(stu);
dataClasses1DataContext1.SubmitChanges();
Console.WriteLine("插入学生成功!");
#endregion


#region 删除学生
var deleteStu = dataClasses1DataContext1.students
    .FirstOrDefault(s => s.sno == 10007);

if (deleteStu != null)
{
    dataClasses1DataContext1.students.DeleteOnSubmit(deleteStu);
    dataClasses1DataContext1.SubmitChanges();
    Console.WriteLine("删除学生成功!");
}
#endregion


#region 修改学生信息
var updateStu = dataClasses1DataContext1.students
    .FirstOrDefault(s => s.sno == 10001);

if (updateStu != null)
{
    updateStu.sname = "张三丰";
    dataClasses1DataContext1.SubmitChanges();
    Console.WriteLine("修改学生信息成功!");
}
#endregion