From 01deec8fb68a81558ea7b74756105aff64a8d1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E9=92=B0?= <2457570067@qq.com> Date: Sun, 25 Jan 2026 20:29:49 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20260119-\346\250\241\345\236\213.md" | 222 ++++++++++++++++++ ...3\347\232\204\351\223\276\346\216\245 .md" | 153 ++++++++++++ ...5\347\232\204\345\233\236\351\241\276 .md" | 55 +++++ ...45\347\232\204\345\233\236\351\241\276.md" | 150 ++++++++++++ 4 files changed, 580 insertions(+) create mode 100644 "\351\273\204\351\222\260/20260119-\346\250\241\345\236\213.md" create mode 100644 "\351\273\204\351\222\260/20260121-MVC\346\225\260\346\215\256\345\272\223\347\232\204\351\223\276\346\216\245 .md" create mode 100644 "\351\273\204\351\222\260/20260122-MVC\346\225\260\346\215\256\345\272\223\347\232\204\351\223\276\346\216\245\347\232\204\345\233\236\351\241\276 .md" create mode 100644 "\351\273\204\351\222\260/20260123-MVC\344\270\255\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\347\232\204\345\242\236\345\210\240\346\224\271\346\237\245\347\232\204\345\233\236\351\241\276.md" diff --git "a/\351\273\204\351\222\260/20260119-\346\250\241\345\236\213.md" "b/\351\273\204\351\222\260/20260119-\346\250\241\345\236\213.md" new file mode 100644 index 0000000..6ab5d35 --- /dev/null +++ "b/\351\273\204\351\222\260/20260119-\346\250\241\345\236\213.md" @@ -0,0 +1,222 @@ +# 笔记 +## 二、核心知识点笔记 +### 知识点1:模型(Model)的核心作用 +1. **什么是模型** + 模型是代表应用程序核心数据的C#类,核心包含三部分内容: + - 数据属性(描述数据的结构与字段) + - 验证规则(确保数据的有效性、完整性) + - 业务逻辑方法(封装与数据相关的业务操作) + > 补充:数据访问逻辑通常分离(如通过EF Core的DbContext),不直接耦合在模型中 + +2. **模型在MVC中的流转位置** + `用户请求 → 控制器 → 模型(获取/处理/验证数据) → 视图(渲染显示数据)` + +3. **模型的核心职责** + - 数据表示:定义数据的结构、类型和默认值 + - 数据验证:内置/自定义规则,过滤无效数据 + - 业务逻辑:封装数据相关的业务操作(如计算、转换) + - 数据持久化:与数据库交互,完成数据的增删改查(通常间接实现) + +4. **模型的三大类型** + - 领域模型(Domain Model):代表核心业务实体(如Student、Course),对应数据库表结构,是应用程序的核心数据载体 + - 视图模型(View Model):为特定视图定制,适配视图的显示与交互需求 + - 输入模型(Input Model):专门用于接收用户输入,与视图模型功能相近,更侧重输入验证 + +### 知识点2:创建模型类(领域模型) +1. **存放位置**:通常放在项目的`Models`文件夹中,为普通的C# POCO类(简单无依赖对象) + +2. **基础示例(Student.cs)** + ```csharp + namespace StudentManagementSystem.Models + { + public class Student + { + public int Id { get; set; } // 主键 + public string Name { get; set; } // 姓名 + public int Age { get; set; } // 年龄 + public string Email { get; set; } // 邮箱 + public DateTime EnrollmentDate { get; set; } // 入学时间 + public string Major { get; set; } // 专业 + } + } + ``` + +3. **模型属性常用类型** + - 简单类型:int、string、DateTime、decimal、bool等 + - 复杂类型:自定义类(如Course)、集合(List)等 + - 可空类型:int?、DateTime?、string?(C# 8.0+可空引用类型,避免空引用异常) + +4. **模型设计原则** + - 单一职责:一个模型只代表一个业务实体,不封装无关业务逻辑 + - 清晰命名:属性名使用语义明确的英文名称(如EnrollmentDate,而非EnrollDate) + - 适当粒度:仅包含必要属性,避免冗余字段,不过度设计 + - 考虑扩展:预留未来可能新增的核心字段(如Student的Phone属性) + +### 知识点3:数据注解(Data Annotations) +1. **什么是数据注解** + 是添加在模型属性上的C#特性(Attribute),核心用于三大场景: + - 定义数据验证规则(核心) + - 控制视图中的数据展示方式 + - 配置与数据库的映射关系 + +2. **常用核心数据注解(三类)** + | 注解类型 | 具体注解 | 核心作用 | + |----------|----------|----------| + | 验证注解 | [Required] | 标记字段为必填项,不可为空 | + | | [StringLength(int max, int min)] | 限制字符串长度(最大/最小) | + | | [Range(int min, int max)] | 限制数值类型的取值范围 | + | | [EmailAddress] | 验证字符串是否符合邮箱格式 | + | | [Compare("PropertyName")] | 比较当前属性与指定属性值是否一致(如确认密码) | + | | [RegularExpression("regex")] | 按正则表达式验证字符串格式(如手机号) | + | 显示注解 | [Display(Name = "中文别名")] | 视图中显示的字段友好名称(如"学生姓名") | + | | [DataType(DataType.Date)] | 指定数据类型,视图自动适配渲染(如日期选择器) | + | | [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")] | 格式化数据显示(如日期、金额) | + | 数据库注解 | [Key] | 标记该属性为数据库表的主键 | + | | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] | 标记主键为自增字段 | + | | [Table("TableName")] | 指定模型对应的数据库表名 | + | | [Column("ColumnName")] | 指定属性对应的数据库列名 | + +3. **自定义验证特性** + 当内置注解无法满足特殊业务需求时,可自定义验证特性(继承`ValidationAttribute`,重写`IsValid`方法): + ```csharp + public class ValidEnrollmentDateAttribute : ValidationAttribute + { + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + // 1. 转换属性值类型 + if (value is DateTime enrollmentDate) + { + // 2. 实现自定义验证逻辑 + if (enrollmentDate > DateTime.Now) + { + return new ValidationResult("入学时间不能晚于当前日期"); + } + if (enrollmentDate.Year < 2000) + { + return new ValidationResult("入学时间不能早于2000年"); + } + } + // 3. 验证通过返回成功 + return ValidationResult.Success; + } + } + ``` + +### 知识点4:视图模型(View Model) +1. **为什么需要视图模型** + - 领域模型可能包含视图不需要的字段,直接使用会暴露核心业务细节 + - 单个视图可能需要组合多个领域模型的数据(如学生+课程信息) + - 视图有专属交互需求(如确认邮箱、同意条款),领域模型无需包含 + - 避免视图与领域模型强耦合,提高项目可维护性 + +2. **存放位置**:通常放在项目的`ViewModels`文件夹中 + +3. **创建示例(StudentCreateViewModel.cs)** + ```csharp + namespace StudentManagementSystem.ViewModels + { + public class StudentCreateViewModel + { + [Required(ErrorMessage = "姓名不能为空")] + [Display(Name = "姓名")] + [StringLength(50, MinimumLength = 2, ErrorMessage = "姓名长度必须在2-50个字符之间")] + public string Name { get; set; } + + [Required(ErrorMessage = "年龄不能为空")] + [Display(Name = "年龄")] + [Range(15, 40, ErrorMessage = "年龄必须在15-40岁之间")] + public int Age { get; set; } + + [Required(ErrorMessage = "邮箱不能为空")] + [Display(Name = "邮箱")] + [EmailAddress(ErrorMessage = "邮箱格式不正确")] + public string Email { get; set; } + + [Required(ErrorMessage = "请选择专业")] + [Display(Name = "专业")] + public string Major { get; set; } + + [Display(Name = "入学时间")] + [DataType(DataType.Date)] + [ValidEnrollmentDate] // 引用自定义验证特性 + public DateTime EnrollmentDate { get; set; } + + // 视图专属属性(领域模型无此字段) + [Display(Name = "确认邮箱")] + [Compare("Email", ErrorMessage = "邮箱与确认邮箱不一致")] + public string ConfirmEmail { get; set; } + + [Display(Name = "我已阅读并同意条款")] + [Range(typeof(bool), "true", "true", ErrorMessage = "必须同意条款")] + public bool AgreeToTerms { get; set; } + } + } + ``` + +### 知识点5:模型绑定(Model Binding) +1. **什么是模型绑定** + ASP.NET Core自动将**HTTP请求数据**映射到**控制器动作参数**的过程,无需手动解析请求数据,提高开发效率。 + +2. **常见绑定来源** + - 路由数据(如URL中的`/Student/Edit/1`中的`1`) + - 查询字符串(如`/Student/List?page=1&size=10`中的`page`和`size`) + - 表单数据(POST请求中的表单提交数据,核心) + - JSON/XML请求体(前后端分离项目常用) + - 请求头、Cookie等 + +3. **常用绑定特性(指定绑定来源)** + | 特性 | 绑定来源 | 适用场景 | + |------|----------|----------| + | [FromQuery] | 查询字符串 | 获取分页、筛选参数 | + | [FromRoute] | 路由数据 | 获取资源ID(如学生ID) | + | [FromForm] | 表单数据 | 接收表单提交的创建/编辑数据 | + | [FromBody] | 请求体(JSON/XML) | 前后端分离项目,接收JSON数据 | + | [FromHeader] | HTTP请求头 | 获取请求头中的自定义信息 | + + 示例: + ```csharp + // id来自路由,student来自请求体JSON + public IActionResult Edit([FromRoute] int id, [FromBody] Student student) + { + // 业务处理 + return RedirectToAction("Index"); + } + ``` + +4. **模型状态(ModelState)** + - 模型绑定完成后,系统会自动根据数据注解验证模型,验证结果存入`ModelState` + - 通过`ModelState.IsValid`判断模型验证是否通过 + - 验证失败:重新返回视图,显示错误信息;验证成功:处理数据并跳转 + + 核心示例: + ```csharp + [HttpPost] + public IActionResult Create(StudentCreateViewModel viewModel) + { + // 验证模型数据是否有效 + if (!ModelState.IsValid) + { + // 验证失败,重新返回创建视图,保留用户输入并显示错误信息 + return View(viewModel); + } + + // 验证成功,将视图模型转换为领域模型,后续存入数据库 + var student = new Student + { + Name = viewModel.Name, + Age = viewModel.Age, + Email = viewModel.Email, + Major = viewModel.Major, + EnrollmentDate = viewModel.EnrollmentDate + }; + + // 业务处理(如添加到数据库) + // _studentRepository.Add(student); + + // 跳转至学生列表页 + return RedirectToAction("Index"); + } + ``` + +## 三、核心总结 +1. 完整流程:`创建模型(领域/视图)→ 添加数据注解 → 控制器接收绑定 → 验证ModelState → 处理数据/返回视图` \ No newline at end of file diff --git "a/\351\273\204\351\222\260/20260121-MVC\346\225\260\346\215\256\345\272\223\347\232\204\351\223\276\346\216\245 .md" "b/\351\273\204\351\222\260/20260121-MVC\346\225\260\346\215\256\345\272\223\347\232\204\351\223\276\346\216\245 .md" new file mode 100644 index 0000000..74d40bf --- /dev/null +++ "b/\351\273\204\351\222\260/20260121-MVC\346\225\260\346\215\256\345\272\223\347\232\204\351\223\276\346\216\245 .md" @@ -0,0 +1,153 @@ +# 笔记 +## 一、核心步骤(Code First 方式) +### 步骤1:安装必要的NuGet包 +```bash +# EF Core SQLite 提供程序(核心) +dotnet add package Microsoft.EntityFrameworkCore.Sqlite + +# EF Core 设计工具(用于迁移) +dotnet add package Microsoft.EntityFrameworkCore.Design + +# (可选)全局安装EF Core命令行工具(首次使用时) +dotnet tool install --global dotnet-ef +``` + +### 步骤2:定义数据库上下文(DbContext) +#### 2.1 创建上下文类 +```csharp +using Microsoft.EntityFrameworkCore; + +namespace 项目命名空间.Data +{ + public class ApplicationDbContext : DbContext + { + // 1. 定义数据表映射(DbSet 对应数据库中的表) + public DbSet Students { get; set; } // 学生表 + public DbSet Vips { get; set; } // 示例:VIP表 + + // 2. 构造函数注入配置 + public ApplicationDbContext(DbContextOptions options) : base(options) + { + } + + // 3. 配置数据库连接(也可在Program.cs中配置,推荐) + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + // 直接配置SQLite连接字符串(备选方案) + if (!optionsBuilder.IsConfigured) + { + optionsBuilder.UseSqlite("Data Source=students.db;Cache=Shared"); + } + } + + // 4. 可选:配置模型(主键、索引、字段属性、种子数据等) + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + // 示例:配置Student实体属性 + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); // 主键 + entity.Property(e => e.Name).IsRequired().HasMaxLength(50); // 姓名必填,最大长度50 + }); + } + } +} +``` + +#### 2.2 配置连接字符串(appsettings.json) +```json +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source=students.db;Cache=Shared" + } +} +``` + +#### 2.3 注册DbContext服务(Program.cs) +```csharp +using Microsoft.EntityFrameworkCore; +using 项目命名空间.Data; + +var builder = WebApplication.CreateBuilder(args); + +// 注册数据库上下文 +builder.Services.AddDbContext(options => +{ + var connStr = builder.Configuration.GetConnectionString("DefaultConnection"); + options.UseSqlite(connStr); // 使用SQLite数据库 +}); +``` + +### 步骤3:执行数据库迁移(Migration) +#### 3.1 迁移前置条件 +- 项目代码无编译错误 +- 应用程序处于停止运行状态 +- 命令行需切换到项目根目录 + +#### 3.2 迁移命令 +```bash +# 1. 创建迁移文件(InitialCreate为迁移名称,可自定义) +dotnet ef migrations add InitialCreate + +# 2. (可选)删除最近一次迁移(若创建错误) +dotnet ef migrations remove + +# 3. 将迁移应用到数据库(生成/更新数据库表) +dotnet ef database update +``` + +### 步骤4:使用DbContext实现CRUD操作 +#### 4.1 注入DbContext到控制器 +```csharp +private readonly ApplicationDbContext _context; + +// 构造函数注入 +public StudentController(ApplicationDbContext context) +{ + _context = context; +} +``` + +#### 4.2 核心CRUD操作 +```csharp +// 1. 创建(新增) +var student = new Student { Name = "张三", Age = 20 }; +_context.Students.Add(student); +await _context.SaveChangesAsync(); // 提交到数据库 + +// 2. 读取(查询) +var singleStudent = await _context.Students.FindAsync(id); // 按主键查询 +var filteredStudents = await _context.Students.Where(s => s.Age > 18).ToListAsync(); // 条件查询 + +// 3. 更新(修改) +student.Name = "李四"; +_context.Students.Update(student); +await _context.SaveChangesAsync(); + +// 4. 删除 +_context.Students.Remove(student); +await _context.SaveChangesAsync(); +``` + +## 二、进阶优化(可选) +### 1. 服务层封装(解耦控制器与数据访问) +- 创建IStudentService接口和StudentService实现类 +- 将CRUD逻辑封装到服务层,控制器仅调用服务 +- 优点:代码复用、便于单元测试、符合分层架构设计 + +### 2. 迁移管理 +- 迁移文件记录模型变更历史,可回滚到指定版本:`dotnet ef database update 迁移名称` +- 生成SQL脚本(不直接执行):`dotnet ef migrations script` + +### 3. 查询优化 +- 延迟加载:默认行为,查询时不立即执行SQL,遍历数据时才执行 +- 立即加载:使用`ToListAsync()`/`FirstAsync()`等立即执行查询 +- 分页查询:`Skip(10).Take(10)`(跳过10条,取10条) +- 字段筛选:`Select(s => new { s.Id, s.Name })`(仅查询指定字段) + +## 三、关键点回顾 +1. **核心依赖**:必须安装`Microsoft.EntityFrameworkCore.Sqlite`和`Microsoft.EntityFrameworkCore.Design`两个包,全局安装ef工具便于命令行操作。 +2. **DbContext**:作为数据库会话的核心,需配置连接字符串、定义DbSet映射数据表,推荐在Program.cs中注册服务。 +3. **迁移流程**:创建迁移(记录模型变更)→ 应用迁移(同步到数据库),是Code First方式的核心步骤。 +4. **CRUD操作**:通过DbContext的Add/Update/Remove方法修改数据,必须调用`SaveChangesAsync()`才能将变更提交到数据库。 +5. **SQLite特性**:单文件存储,连接字符串格式为`Data Source=文件名.db`,无需额外配置数据库服务器。 \ No newline at end of file diff --git "a/\351\273\204\351\222\260/20260122-MVC\346\225\260\346\215\256\345\272\223\347\232\204\351\223\276\346\216\245\347\232\204\345\233\236\351\241\276 .md" "b/\351\273\204\351\222\260/20260122-MVC\346\225\260\346\215\256\345\272\223\347\232\204\351\223\276\346\216\245\347\232\204\345\233\236\351\241\276 .md" new file mode 100644 index 0000000..28082ce --- /dev/null +++ "b/\351\273\204\351\222\260/20260122-MVC\346\225\260\346\215\256\345\272\223\347\232\204\351\223\276\346\216\245\347\232\204\345\233\236\351\241\276 .md" @@ -0,0 +1,55 @@ +# 笔记 + +## 一、核心知识点梳理 + +### 1. SQLite 数据库特性 +轻量级嵌入式数据库,无需安装服务器,以单文件(如students.db)存储数据,配置简单,适合开发、测试及小型应用场景,连接字符串核心配置为`Data Source=数据库文件名.db`。 + +## 二、关键实施步骤回顾 +### 1. 环境准备:安装依赖包 +通过命令行安装EF Core核心依赖,确保项目支持SQLite连接与迁移功能: +```bash +# 安装SQLite数据库提供程序 +dotnet add package Microsoft.EntityFrameworkCore.Sqlite +# 安装EF Core设计工具(支持迁移命令) +dotnet add package Microsoft.EntityFrameworkCore.Design +# (首次使用)全局安装EF Core命令行工具 +dotnet tool install --global dotnet-ef +``` + +### 2. 核心配置:定义数据库上下文 +- **创建DbContext类**:继承`DbContext`,定义`DbSet`属性映射Students表,通过构造函数注入配置,可选重写`OnModelCreating`配置实体属性(主键、索引、字段约束、种子数据等)。 +- **配置连接字符串**:在`appsettings.json`中添加`ConnectionStrings`节点,指定SQLite数据库文件路径。 +- **注册服务**:在`Program.cs`中注册`ApplicationDbContext`服务,关联配置文件中的连接字符串。 + +### 3. 数据库迁移:同步模型到数据库 +迁移是Code First的核心步骤,需保证项目无编译错误且未运行: +```bash +# 创建迁移文件(记录模型变更,InitialCreate为自定义迁移名称) +dotnet ef migrations add InitialCreate +# 应用迁移到数据库(生成/更新表结构) +dotnet ef database update +``` + +### 4. 数据操作:实现CRUD功能 +通过注入`ApplicationDbContext`,在控制器/服务层完成基础数据操作,核心逻辑如下: +```csharp +// 新增:添加实体并提交 +var student = new Student { Name = "张三", Age = 20 }; +_context.Students.Add(student); +await _context.SaveChangesAsync(); + +// 查询:按主键/条件查询 +var singleStudent = await _context.Students.FindAsync(id); +var filteredStudents = await _context.Students.Where(s => s.Age > 18).ToListAsync(); + +// 更新:修改实体属性后提交 +student.Name = "李四"; +_context.Students.Update(student); +await _context.SaveChangesAsync(); + +// 删除:移除实体并提交 +_context.Students.Remove(student); +await _context.SaveChangesAsync(); +``` + diff --git "a/\351\273\204\351\222\260/20260123-MVC\344\270\255\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\347\232\204\345\242\236\345\210\240\346\224\271\346\237\245\347\232\204\345\233\236\351\241\276.md" "b/\351\273\204\351\222\260/20260123-MVC\344\270\255\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\347\232\204\345\242\236\345\210\240\346\224\271\346\237\245\347\232\204\345\233\236\351\241\276.md" new file mode 100644 index 0000000..59b4c16 --- /dev/null +++ "b/\351\273\204\351\222\260/20260123-MVC\344\270\255\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\347\232\204\345\242\236\345\210\240\346\224\271\346\237\245\347\232\204\345\233\236\351\241\276.md" @@ -0,0 +1,150 @@ +# 笔记 + +## 一、核心操作实现(以Student实体为例) +### 1. 查询(Read):从数据库获取数据 +#### 1.1 基础查询方式 +```csharp +// 注入数据库上下文 +private readonly ApplicationDbContext _context; +public StudentController(ApplicationDbContext context) +{ + _context = context; +} + +// 1. 按主键查询(单条数据):FindAsync是EF Core内置主键查询方法,性能最优 +public async Task Details(int id) +{ + var student = await _context.Students.FindAsync(id); + if (student == null) return NotFound(); // 无数据返回404 + return View(student); +} + +// 2. 条件查询(多条数据):使用Linq表达式筛选 +public async Task Index(string? name, int? minAge) +{ + // 构建查询(延迟加载,未执行SQL) + var query = _context.Students.AsQueryable(); + + // 动态添加筛选条件 + if (!string.IsNullOrEmpty(name)) + { + query = query.Where(s => s.Name.Contains(name)); + } + if (minAge.HasValue) + { + query = query.Where(s => s.Age >= minAge.Value); + } + + // 执行查询(ToListAsync触发SQL执行,立即加载) + var students = await query.OrderByDescending(s => s.CreatedAt).ToListAsync(); + return View(students); +} +``` + +#### 1.2 查询优化要点 +- **延迟加载**:Linq查询默认延迟执行,仅调用`ToListAsync()`/`FirstAsync()`等方法时才生成并执行SQL。 +- **字段筛选**:仅查询需要的字段,减少数据传输(如`Select(s => new { s.Id, s.Name })`)。 +- **分页查询**:使用`Skip(页码*页大小).Take(页大小)`实现分页,避免一次性加载全表数据。 + +### 2. 修改(Update):更新数据库已有数据 +#### 2.1 核心实现逻辑 +```csharp +// GET:加载待编辑数据到页面 +public async Task Edit(int id) +{ + var student = await _context.Students.FindAsync(id); + if (student == null) return NotFound(); + // 转换为视图模型(可选,解耦实体与视图) + var viewModel = new StudentEditViewModel + { + Id = student.Id, + Name = student.Name, + Age = student.Age, + Email = student.Email, + Major = student.Major + }; + return View(viewModel); +} + +// POST:提交修改并保存到数据库 +[HttpPost] +[ValidateAntiForgeryToken] // 防跨站请求伪造 +public async Task Edit(int id, StudentEditViewModel viewModel) +{ + if (id != viewModel.Id) return NotFound(); // 主键不一致,返回404 + if (!ModelState.IsValid) return View(viewModel); // 验证失败,返回表单 + + try + { + // 1. 查找数据库中原有数据 + var existingStudent = await _context.Students.FindAsync(id); + if (existingStudent == null) return NotFound(); + + // 2. 更新属性(仅修改需要变更的字段) + existingStudent.Name = viewModel.Name; + existingStudent.Age = viewModel.Age; + existingStudent.Email = viewModel.Email; + existingStudent.Major = viewModel.Major; + existingStudent.UpdatedAt = DateTime.Now; // 手动更新修改时间 + + // 3. 提交变更(EF Core自动跟踪实体状态,无需显式调用Update) + await _context.SaveChangesAsync(); + + TempData["SuccessMessage"] = "学生信息更新成功!"; + return RedirectToAction(nameof(Index)); + } + catch (Exception ex) + { + ModelState.AddModelError("", "更新失败:" + ex.Message); + return View(viewModel); + } +} +``` + +#### 2.2 关键注意事项 +- **实体状态跟踪**:通过`FindAsync()`获取的实体默认被EF Core跟踪,修改属性后直接调用`SaveChangesAsync()`即可生效,无需`_context.Students.Update()`。 +- **验证前置**:更新前必须做模型验证(`ModelState.IsValid`),避免非法数据写入数据库。 +- **异常处理**:捕获更新过程中的异常(如唯一键冲突),友好提示用户。 + +### 3. 删除(Delete):移除数据库中的数据 +#### 3.1 核心实现逻辑(含确认环节) +```csharp +// GET:展示删除确认页面 +public async Task Delete(int id) +{ + var student = await _context.Students.FindAsync(id); + if (student == null) return NotFound(); + return View(student); +} + +// POST:确认删除并提交到数据库(ActionName指定匹配Delete) +[HttpPost, ActionName("Delete")] +[ValidateAntiForgeryToken] +public async Task DeleteConfirmed(int id) +{ + try + { + var student = await _context.Students.FindAsync(id); + if (student == null) return NotFound(); + + // 1. 从DbSet中移除实体 + _context.Students.Remove(student); + // 2. 提交删除操作 + await _context.SaveChangesAsync(); + + TempData["SuccessMessage"] = $"学生{student.Name}删除成功!"; + return RedirectToAction(nameof(Index)); + } + catch (Exception ex) + { + TempData["ErrorMessage"] = "删除失败:" + ex.Message; + return RedirectToAction(nameof(Index)); + } +} +``` + +## 二、关键点回顾 +1. **查询核心**:主键查询用`FindAsync()`,条件查询用Linq+`Where()`,所有查询需通过`Async`方法异步执行,优化性能。 +2. **修改核心**:先查库获取原实体→更新属性→调用`SaveChangesAsync()`,EF Core自动跟踪状态,无需显式`Update()`。 +3. **删除核心**:物理删除需先查实体→调用`Remove()`→提交变更;生产环境优先软删除,保障数据安全。 +4. **数据提交**:所有删改操作必须调用`SaveChangesAsync()`才能生效,仅修改/移除实体不会同步到数据库。 \ No newline at end of file -- Gitee