From d286f1d14c68be12888d4a7b44efe70ac19d9394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=92=E9=9C=B2=E7=91=B6?= <446345442@qq.com> Date: Sun, 7 Jun 2026 22:00:55 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...00\350\257\276\347\273\203\344\271\240.md" | 45 +++++ ...ler \346\216\245\345\210\260 DbContext.md" | 177 ++++++++++++++++++ .../20260604\350\277\201\347\247\273.md" | 72 +++++++ ...36\344\275\223\345\273\272\346\250\241.md" | 53 ++++++ 4 files changed, 347 insertions(+) create mode 100644 "\350\210\222\351\234\262\347\221\266/20260601\347\254\254\344\270\200\350\257\276\347\273\203\344\271\240.md" create mode 100644 "\350\210\222\351\234\262\347\221\266/20260603\346\212\212 Controller \346\216\245\345\210\260 DbContext.md" create mode 100644 "\350\210\222\351\234\262\347\221\266/20260604\350\277\201\347\247\273.md" create mode 100644 "\350\210\222\351\234\262\347\221\266/20260605\345\256\236\344\275\223\345\273\272\346\250\241.md" diff --git "a/\350\210\222\351\234\262\347\221\266/20260601\347\254\254\344\270\200\350\257\276\347\273\203\344\271\240.md" "b/\350\210\222\351\234\262\347\221\266/20260601\347\254\254\344\270\200\350\257\276\347\273\203\344\271\240.md" new file mode 100644 index 0000000..2f72bae --- /dev/null +++ "b/\350\210\222\351\234\262\347\221\266/20260601\347\254\254\344\270\200\350\257\276\347\273\203\344\271\240.md" @@ -0,0 +1,45 @@ +## 笔记 +- 第一步:创建项目 +dotnet run webapi -n 项目名 --use-controllers +- 第二步:安装包 +dotnet add package Microsoft.EntityFrameworkCore.Sqlite -v 8 +dotnet add package Microsoft.EntityFrameworkCore.Design -v 8 +- 第三步:安装dotnet-ef包 +dotnet tool install --global dotnet-ef +- 第四:准备sqlite文件 + "ConnectionStrings": { + "Default": "Data Source=myshop.db" + } +- 第五: +``` +新建 Data/AppDbContext.cs: + +using Microsoft.EntityFrameworkCore; +using MyShopApi.Models; + +namespace MyShopApi.Data; + +// DbContext = 一个数据库会话,封装连接、跟踪实体变更、生成 SQL +public class AppDbContext : DbContext +{ + // 通过构造函数注入配置(连接字符串) + public AppDbContext(DbContextOptions options) : base(options) { } + + // DbSet 代表一张表;后续 CRUD 都通过它 + public DbSet Categories => Set(); + public DbSet Products => Set(); +} +``` +- GET(取快递) +快递场景:去驿站查询、拿走已经存在的快递,只查看不修改包裹。 +- HTTP 含义:向服务器查询获取数据,不会修改服务器数据;参数放在地址栏,适合查询、搜索。 +- POST(寄新快递) +快递场景:线下网点寄出一个全新包裹,驿站新增一件快递。 +- HTTP 含义:向服务器新增数据,提交表单、注册账号、发布文章;数据放在请求体,隐私安全更高。 +- PUT(更换完整快递) +快递场景:驿站原有一个包裹,你直接拿一个全新包裹完整替换它,旧包裹直接作废。 +- HTTP 含义:全量更新服务器资源,完整替换一条数据。 +- DELETE(退回 / 销毁快递) +快递场景:联系驿站取消快递、直接销毁包裹,驿站删除这件快递记录。 +- HTTP 含义:删除服务器上指定的资源数据。 +GET 和 POST 核心区别(快递对比) \ No newline at end of file diff --git "a/\350\210\222\351\234\262\347\221\266/20260603\346\212\212 Controller \346\216\245\345\210\260 DbContext.md" "b/\350\210\222\351\234\262\347\221\266/20260603\346\212\212 Controller \346\216\245\345\210\260 DbContext.md" new file mode 100644 index 0000000..d7b79b0 --- /dev/null +++ "b/\350\210\222\351\234\262\347\221\266/20260603\346\212\212 Controller \346\216\245\345\210\260 DbContext.md" @@ -0,0 +1,177 @@ +### 笔记 +注入 DbContext +Controller 通过构造函数注入拿到 AppDbContext 实例: + +public class CategoriesController : ControllerBase +{ + private readonly AppDbContext _db; + + public CategoriesController(AppDbContext db) + { + _db = db; + } + + // ... +} +_db 是 DbContext,实现了 IDisposable,不需要手动释放——ASP.NET Core 的 DI 容器在请求结束时自动处理。 + +改造后的 CategoriesController +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MyShopApi.Data; +using MyShopApi.Models; + +namespace MyShopApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CategoriesController : ControllerBase +{ + private readonly AppDbContext _db; + + public CategoriesController(AppDbContext db) + { + _db = db; + } + + // GET /api/categories + [HttpGet] + public async Task>> GetAll() + { + var categories = await _db.Categories.ToListAsync(); + return Ok(categories); + } + + // GET /api/categories/{id} + [HttpGet("{id:int}")] + public async Task> GetById(int id) + { + var category = await _db.Categories.FindAsync(id); + if (category is null) return NotFound(); + return Ok(category); + } + + // POST /api/categories + [HttpPost] + public async Task> Create([FromBody] Category input) + { + _db.Categories.Add(input); + await _db.SaveChangesAsync(); + return CreatedAtAction(nameof(GetById), new { id = input.Id }, input); + } + + // PUT /api/categories/{id} + [HttpPut("{id:int}")] + public async Task Update(int id, [FromBody] Category input) + { + var existing = await _db.Categories.FindAsync(id); + if (existing is null) return NotFound(); + + existing.Name = input.Name; + existing.Description = input.Description; + + await _db.SaveChangesAsync(); + return NoContent(); + } + + // DELETE /api/categories/{id} + [HttpDelete("{id:int}")] + public async Task Delete(int id) + { + var existing = await _db.Categories.FindAsync(id); + if (existing is null) return NotFound(); + + _db.Categories.Remove(existing); + await _db.SaveChangesAsync(); + return NoContent(); + } +} +关键点解读 +异步(async/await) +EF Core 的数据库操作都是 IO 密集型,必须用异步方法避免阻塞线程池: + +.ToListAsync()、.FindAsync()、.FirstOrDefaultAsync() +await _db.SaveChangesAsync() +类比 Express:相当于 await db.query(),不阻塞事件循环。 + +Change Tracking +EF Core 会跟踪从数据库读出来的实体。如果改了它的属性,SaveChanges 自动生成 UPDATE 语句。 + +Find / FirstOrDefault 读出来的实体已被跟踪 +直接 existing.Name = input.Name,EF 知道这一行变了 +SaveChanges 把变更落库 +如果用 AsNoTracking()(第 4 章会用到),就不会被跟踪,需要手动 Update(): + +var category = await _db.Categories.AsNoTracking().FirstOrDefaultAsync(c => c.Id == id); +category.Name = input.Name; +_db.Categories.Update(category); // 必须显式 Update +await _db.SaveChangesAsync(); +Add / Remove +Add 把实体标记为 Added 状态,下次 SaveChanges 生成 INSERT +Remove 标记为 Deleted,生成 DELETE +新建 ProductsController +按同样模式新建 Controllers/ProductsController.cs: + +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MyShopApi.Data; +using MyShopApi.Models; + +namespace MyShopApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class ProductsController : ControllerBase +{ + private readonly AppDbContext _db; + + public ProductsController(AppDbContext db) => _db = db; + + [HttpGet] + public async Task>> GetAll() + { + var products = await _db.Products.ToListAsync(); + return Ok(products); + } + + [HttpGet("{id:int}")] + public async Task> GetById(int id) + { + var product = await _db.Products.FindAsync(id); + if (product is null) return NotFound(); + return Ok(product); + } + + [HttpPost] + public async Task> Create([FromBody] Product input) + { + _db.Products.Add(input); + await _db.SaveChangesAsync(); + return CreatedAtAction(nameof(GetById), new { id = input.Id }, input); + } + + [HttpPut("{id:int}")] + public async Task Update(int id, [FromBody] Product input) + { + var existing = await _db.Products.FindAsync(id); + if (existing is null) return NotFound(); + + existing.Name = input.Name; + existing.Price = input.Price; + existing.Stock = input.Stock; + existing.CategoryId = input.CategoryId; + + await _db.SaveChangesAsync(); + return NoContent(); + } + + [HttpDelete("{id:int}")] + public async Task Delete(int id) + { + var existing = await _db.Products.FindAsync(id); + if (existing is null) return NotFound(); + _db.Products.Remove(existing); + await _db.SaveChangesAsync(); + return NoContent(); + } +} \ No newline at end of file diff --git "a/\350\210\222\351\234\262\347\221\266/20260604\350\277\201\347\247\273.md" "b/\350\210\222\351\234\262\347\221\266/20260604\350\277\201\347\247\273.md" new file mode 100644 index 0000000..8824dd1 --- /dev/null +++ "b/\350\210\222\351\234\262\347\221\266/20260604\350\277\201\347\247\273.md" @@ -0,0 +1,72 @@ +### 笔记 +迁移(Migration)是什么 +迁移 = 把 C# 实体的结构变化翻译成数据库的 DDL(建表、改字段、加索引)。一个迁移对应一次结构变更,可以逐步应用到数据库,也可以回滚。 + +类比: + +迁移 ≈ Git 提交,每次模型变更是一次 commit +dotnet ef migrations add X ≈ git commit +dotnet ef database update ≈ git push(应用到环境) +第一次迁移:Init +在项目根目录执行: + +dotnet ef migrations add Init +成功后会生成一个 Migrations/ 目录,里面有三个文件: + +Migrations/ +├── 20250101000000_Init.cs # 迁移主体:Up() / Down() +├── 20250101000000_Init.Designer.cs # 设计时快照:当前模型状态 +└── AppDbContextModelSnapshot.cs # 整个模型快照(与 Designer 配合) +打开 20250101000000_Init.cs: + +public partial class Init : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 50, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 200, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Products", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 100, nullable: false), + Price = table.Column(type: "TEXT", nullable: false), + Stock = table.Column(type: "INTEGER", nullable: false), + CategoryId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Products", x => x.Id); + table.ForeignKey( + name: "FK_Products_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Products_Name", + table: "Products", + column: "Name"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // 回滚操作:删表、删索引 + } +} \ No newline at end of file diff --git "a/\350\210\222\351\234\262\347\221\266/20260605\345\256\236\344\275\223\345\273\272\346\250\241.md" "b/\350\210\222\351\234\262\347\221\266/20260605\345\256\236\344\275\223\345\273\272\346\250\241.md" new file mode 100644 index 0000000..dfd7e69 --- /dev/null +++ "b/\350\210\222\351\234\262\347\221\266/20260605\345\256\236\344\275\223\345\273\272\346\250\241.md" @@ -0,0 +1,53 @@ +实体 vs DTO +先理清一个重要区分: + +实体(Entity):和数据库表结构一一对应,DbSet 用的类型 +DTO(Data Transfer Object):和 HTTP 接口的请求/响应 JSON 对应,给前端看的 +本章先只用实体。两者混用会带来各种"Id 字段被前端传 0 进来"等麻烦——第 3 章引入 DTO。 + +更新 Category +Models/Category.cs: + +using System.ComponentModel.DataAnnotations; + +namespace MyShopApi.Models; + +public class Category +{ + public int Id { get; set; } + + [Required, StringLength(50)] + public string Name { get; set; } = string.Empty; + + [StringLength(200)] + public string? Description { get; set; } + + // 导航属性:一对多的"一"端,集合类型;多端通过引用属性 Product.Category 引用回来 + public ICollection Products { get; set; } = new List(); +} +新建 Product +Models/Product.cs: + +using System.ComponentModel.DataAnnotations; + +namespace MyShopApi.Models; + +public class Product +{ + public int Id { get; set; } + + [Required, StringLength(100)] + public string Name { get; set; } = string.Empty; + + [Range(0, 1000000)] + public decimal Price { get; set; } + + [Range(0, int.MaxValue)] + public int Stock { get; set; } + + // 外键:约定命名为 <导航属性名>Id / <类型名>Id 即可被 EF 识别 + public int CategoryId { get; set; } + + // 导航属性:指向所属分类 + public Category? Category { get; set; } +} -- Gitee From 5e4981286f62dff03b4e8db0bc9ce4fe153357d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=92=E9=9C=B2=E7=91=B6?= <446345442@qq.com> Date: Sun, 14 Jun 2026 20:28:38 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...24\350\256\260\346\255\245\351\252\244.md" | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 "\350\210\222\351\234\262\347\221\266/20260608\347\254\224\350\256\260\346\255\245\351\252\244.md" diff --git "a/\350\210\222\351\234\262\347\221\266/20260608\347\254\224\350\256\260\346\255\245\351\252\244.md" "b/\350\210\222\351\234\262\347\221\266/20260608\347\254\224\350\256\260\346\255\245\351\252\244.md" new file mode 100644 index 0000000..af6f5d9 --- /dev/null +++ "b/\350\210\222\351\234\262\347\221\266/20260608\347\254\224\350\256\260\346\255\245\351\252\244.md" @@ -0,0 +1,122 @@ +## 笔记 +创项目-安装两个package-安装工具tool-创文件夹(Model和Data)-migration-update + + +/Model/Student.cs +``` +namespace StudentManager.Model; +public class Student{ + public int Id{get;set;} + public string StudentName{get;set;}=null!; + public string? NickName{get;set;} +} +``` + +Data/AppDbContext.cs +``` +using Microsoft.EntityFrameworkCore; +using StudentManager.Model; +namespace StudentManager.Data; + +public class AppDbContext : DbContext +{ + public AppDbContext(DbContextOptions opts):base(opts){} + public DbSetStudents {get;set;} + +} +``` + +program.cs +``` +builder.Services.AddDbContext(p => +{ + // 获取连接字符串 + var conString=builder.Configuration.GetConnectionString("sqlite"); + // 表示注入的实例使用sqlite这个数据库驱动,并且使用上面获得的数据连接字符串 + p.UseSqlite(conString); +}); +``` + +json文件中加 +``` + "ConnectionStrings": { + "sqlite":"data source=./db.db;"} +``` + +弄完以上的代码再迁移 +``` +dotnet ef migrations add 名(首字母大写) +``` + +迁移完更新 +``` +dotnet ef database update +``` + +之后就只需要在控制器里做增删改查的代码了 +Controllers/StudentController.cs +``` +using Microsoft.AspNetCore.Mvc; +using StudentManager.Data; +using StudentManager.Model; + +namespace StudentManager.Controllers; + +[ApiController] +[Route("[controller]")] +public class StudentsController : ControllerBase +{ + private readonly AppDbContext _db; + + public StudentsController(AppDbContext db) + { + _db = db; + } + + [HttpGet()] + public IEnumerable Get() + { + var list = _db.Students.ToList(); + return list; + } + + [HttpGet("{id}")] + public IActionResult Get(int id) + { + var obj = _db.Students.Find(id); + return Ok(obj); + } + [HttpPost()] + public Student Get(Student student)// 这里我们直接返回学生实体类型,要不然好像会出现序列循环引用问题 + { + _db.Students.Add(student); + _db.SaveChanges(); + return student; + } + [HttpPut("{id}")] + public Student? Get(int id, Student student)// 这里我们直接返回学生实体类型,要不然好像会出现序列循环引用问题 + { + var obj = _db.Students.Find(id); + if (obj is null) + { + return null; + } + obj.StudentName=student.StudentName; + obj.Nickname=student.Nickname; + _db.SaveChanges(); + return student; + } + [HttpDelete("{id}")] + public Student? Del(int id)// 这里我们直接返回学生实体类型,要不然好像会出现序列循环引用问题 + { + var obj = _db.Students.Find(id); + if (obj is null) + { + return null; + } + _db.Students.Remove(obj); + _db.SaveChanges(); + return obj; + } +} +``` \ No newline at end of file -- Gitee