news 2026/8/27 7:30:41

.NET 10 Web API 从零搭建:EF Core + SQL Server + DTO 实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
.NET 10 Web API 从零搭建:EF Core + SQL Server + DTO 实战指南

很多 .NET 开发者第一次从“写完接口能跑”走向“认真设计接口”时,都会遇到一个相似的尴尬:Controller 里到底要不要用 DTO?EF Core 对应的数据库上下文放哪一层?SQL Server 连接字符串里的参数为什么总是配不对?这些问题单独拎出来都不难,组合在一起却容易让人无从下手,最后变成“只要能跑就行”,等项目进入维护期后才开始后悔。

这篇文章不打算介绍 .NET 10 的某个炫酷新特性,而是把一条从零到一、能在真实项目里长期维护的路径完整走一遍:用 Controller 搭建 RESTful Web API,用 EF Core 访问 SQL Server,用 DTO 隔离数据模型和接口模型,同时解释每个选择的理由和真正容易踩坑的地方。读完这篇文章,你应该能自己搭出一个结构清晰、可测试、可扩展的 .NET 10 Web API 项目骨架。如果你正准备开始一个新的企业级项目,或者正在整理团队的基础工程模板,这篇文章应该对你有直接帮助。

1. 从零搭建之前:先想清楚这套组合适合谁

先说结论:.NET 10 + EF Core + SQL Server + DTO 这套组合,最适合的是企业内部管理系统、业务中台、 ERP/进销存、订单管理这类“业务规则复杂、字段多、关系多、需要长期维护”的项目。它并不适合所有场景,比如高并发短链接服务、边缘计算网关、极简微服务 Demo,这些场景有更轻量或更专用的技术栈。

为什么这样说?因为这套组合的核心优势是“工程化”,而不是“性能极限”。

  • .NET 10 是 LTS(长期支持)版本,适合做底层依赖稳定、需要多年维护的产品线。
  • EF Core 提供了事务、迁移、模型映射、关系管理,可以极大减少手写数据访问代码的工作量。
  • SQL Server 在企业环境中使用广泛,成熟的运维体系、备份恢复、权限管理、BI 工具链都是它的红利。
  • 在 Controller 层引入 DTO,则能让你在数据模型和接口契约之间建立一道明确的边界。

当然,如果你只是想做一个几十个接口的内部工具,直接用 Minimal API + Dapper 反而更快。但如果你预见到这个项目会不断加需求、会有多个人协作、需要前端开发对齐接口文档,那么从一开始就使用 Controller + DTO 的组织方式,比“先爽一把再重构”要划算得多。

这里真正容易踩坑的地方是:很多团队会把“能用 EF Core 操作数据库”当作目标,结果 Controller 里直接返回实体对象,数据表结构一变,接口返回结构跟着变,前端瞬间炸掉。DTO 不是可有可无的封装,它是 API 和数据库之间的稳定契约。所以本文会把 DTO 作为核心组成部分来写,而不是最后顺带提一句。

2. 核心概念:四个角色各管什么

在一套 Web API 里,Controller、EF Core、SQL Server、DTO 是四个分工明确的角色。把它们的职责边界搞清楚,后面写代码才不会混成一团。

2.1 Controller 是 HTTP 层

Controller 负责接收 HTTP 请求、调用业务逻辑、返回 HTTP 响应。它不应该关心 SQL 怎么写、表结构是什么样,只应该关心“客户端传进来的数据是否合法”“该调用哪个领域方法”“该返回 200 还是 404”。在 ASP.NET Core 中,Controller 通过路由和模型绑定自动完成参数映射,配合[ApiController]特性还能自动做模型验证。

2.2 EF Core 是数据访问层

EF Core 是微软官方的 ORM 框架,它的作用是把 C# 对象映射成数据库表记录,并把 LINQ 查询转换为 SQL 语句。你不再需要手写SqlConnectionSqlCommandDataReader那一套样板代码。EF Core 还提供了 Migrations(迁移)机制,可以像管理代码版本一样管理数据库结构变更。

2.3 SQL Server 是持久化存储

SQL Server 负责最终保存数据、保障事务、提供索引和查询优化。EF Core 只是访问工具,数据库的性能和安全性仍然由 SQL Server 本身以及 DBA 的配置决定。不要以为用了 ORM 就不需要关心数据库索引和连接池。

2.4 DTO 是 API 契约

DTO(Data Transfer Object,数据传输对象)是定义在 HTTP 接口边界上的数据模型。它的价值在于:数据库表结构是内部实现,可以随时调整;而接口返回格式是对前端公开的承诺,不能随意变更。DTO 可以把数据库字段的变更挡在 API 层之外。

为了便于理解,可以看成:

角色类比职责
Controller前台接待处理请求、参数校验、返回状态码
EF Core业务助理把 C# 操作翻译成数据库操作
SQL Server档案库真正存储和管理数据
DTO对外合同定义接口层的数据格式

这四个角色缺一不可,混乱的根源通常在于“控制器直接操作数据库并返回实体”。本文会用一套完整的示例展示如何隔离它们。

3. 环境准备与项目创建

3.1 环境清单

在开始之前,建议先确认你的开发环境:

  • 操作系统:Windows 10/11 或 Windows Server;macOS/Linux 可以开发,但连接 SQL Server 需要额外的驱动配置。
  • .NET SDK:需要安装 .NET 10 SDK。安装完成后可以在命令行执行dotnet --version确认。
  • IDE:推荐 Visual Studio 2022(17.x 以上)或 VS Code + C# Dev Kit。VS Code 轻量,Visual Studio 对调试和模板集成更方便。
  • 数据库:推荐安装 SQL Server Developer Edition 或 SQL Server Express LocalDB。开发阶段用 LocalDB 最省事,但要注意 LocalDB 只适合本机开发,不适合生产。
  • EF Core 工具:命令行执行dotnet tool install --global dotnet-ef,用于执行迁移命令。

3.2 验证安装

打开命令行终端,依次执行:

dotnet --version

能输出版本号说明 SDK 正常。再执行:

dotnet ef --version

如果提示找不到命令,说明 dotnet-ef 工具还没安装或 PATH 没生效。执行下面的命令安装:

dotnet tool install --global dotnet-ef

这里需要注意:dotnet ef工具的版本最好和项目引用的 Microsoft.EntityFrameworkCore 包版本保持一致,否则可能在执行迁移时出现兼容性警告。

3.3 创建 Web API 项目

使用dotnet new命令创建项目:

dotnet new webapi -n BookStore.Api -o BookStore.Api cd BookStore.Api

创建完成后,项目里会包含默认的Program.csControllers目录、appsettings.json和示例的天气接口。模板自带的WeatherForecast相关代码可以直接删除,避免干扰后续步骤。

项目结构如下:

BookStore.Api/ ├── Controllers/ ├── Models/ ├── Data/ ├── Dtos/ ├── Program.cs ├── appsettings.json └── BookStore.Api.csproj

这里我提前规划了几个目录,虽然目前还不存在,但后面会依次创建:Models放实体类,Data放 DbContext,Dtos放 DTO 类,Controllers放 API 控制器。

创建完项目后先跑一次:

dotnet build

确保基础模板能编译通过,再往下走。这样后面出现问题,至少可以排除“项目本身没建好”的干扰。

4. 配置 EF Core 与 SQL Server

4.1 安装 NuGet 包

在项目目录下执行以下命令,安装 EF Core 相关包:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Design

第一个包是 SQL Server 驱动和 EF Core 的核心能力;第二个包提供了dotnet ef迁移命令在设计时所需的功能。如果你的项目需要显式控制迁移配置文件,还要用到Microsoft.EntityFrameworkCore.Tools,不过在命令行场景下Design包已经足够。

4.2 配置连接字符串

打开appsettings.json,加入连接字符串:

{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=BookStoreDb;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True" } }

如果你本机安装的是 SQL Server Express 实例,连接字符串可以写成类似:

"DefaultConnection": "Server=localhost\\SQLEXPRESS;Database=BookStoreDb;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True"

这里的Server指定 SQL Server 实例名,Trusted_Connection=True表示使用 Windows 身份验证。如果你使用 SQL Server 账号密码登录,则要改成:

"DefaultConnection": "Server=localhost;Database=BookStoreDb;User Id=sa;Password=你的密码;TrustServerCertificate=True"

重要提醒:不要把生产环境的连接字符串直接写在appsettings.json并提交到代码仓库。开发环境的默认值可以放,但要通过环境变量、用户机密或配置中心覆盖生产配置。sa账号是数据库超级管理员,生产环境不要使用sa作为 API 的连接账号,应该为应用单独创建最小权限账号。

4.3 注册 DbContext

Program.cs中注册 DbContext 服务。这里我会把 EF Core 的配置统一放到IServiceCollection的扩展方法里,保持Program.cs简洁。

在项目根目录创建Extensions/ServiceCollectionExtensions.cs

using BookStore.Api.Data; using Microsoft.EntityFrameworkCore; namespace BookStore.Api.Extensions; public static class ServiceCollectionExtensions { public static IServiceCollection AddDatabase(this IServiceCollection services, IConfiguration configuration) { var connectionString = configuration.GetConnectionString("DefaultConnection"); services.AddDbContext<BookStoreContext>(options => options.UseSqlServer(connectionString)); return services; } }

然后在Program.cs中调用:

using BookStore.Api.Extensions; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddDatabase(builder.Configuration); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();

这个Program.cs文件是模板生成的精简版。使用扩展方法的好处是:将来换数据库、加缓存、加认证中间件,都不会把Program.cs无限膨胀,每个关注点都能在不同的扩展类里找到。

5. 定义实体、DbContext 与 DTO

5.1 实体类

实体类对应数据库表的结构。我以一个简单的图书管理场景为例,创建一个Book实体。

文件路径:Models/Book.cs

namespace BookStore.Api.Models; public class Book { public int Id { get; set; } public string Title { get; set; } = string.Empty; public string? Author { get; set; } public string? ISBN { get; set; } public DateTime PublishDate { get; set; } public decimal Price { get; set; } public DateTime CreatedAt { get; set; } }

这里有几个细节:

  • Title使用string.Empty初始化,并且设计为不可为空,因为图书标题是必填字段。
  • AuthorISBN使用了string?,允许为空。
  • CreatedAt是创建时间,可以在保存时统一赋值,而不是让前端传入。

5.2 DbContext

DbContext 是 EF Core 与数据库之间的桥梁。它负责跟踪实体状态、生成并执行 SQL、管理事务。

文件路径:Data/BookStoreContext.cs

using BookStore.Api.Models; using Microsoft.EntityFrameworkCore; namespace BookStore.Api.Data; public class BookStoreContext : DbContext { public BookStoreContext(DbContextOptions<BookStoreContext> options) : base(options) { } public DbSet<Book> Books => Set<Book>(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Book>(entity => { entity.HasKey(b => b.Id); entity.Property(b => b.Title).IsRequired().HasMaxLength(200); entity.Property(b => b.Author).HasMaxLength(100); entity.Property(b => b.ISBN).HasMaxLength(20); entity.Property(b => b.Price).HasPrecision(18, 2); entity.Property(b => b.CreatedAt).HasDefaultValueSql("GETUTCDATE()"); }); base.OnModelCreating(modelBuilder); } }

OnModelCreating中配置了主要约束:主键、必填字段、字段长度、价格精度、默认值。这样可以确保数据库表结构在创建时就是规范的,而不是依赖 EF Core 的默认推断。

有一点需要理解:OnModelCreating中的配置可以全部用 Data Annotation(数据注解)写在实体类上,但 Fluent API(如上面代码)配置更集中,适合统一管理。

5.3 DTO 类

DTO 的作用是定义接口层的数据契约。我这里按常见的 REST API 习惯拆分三类:

  • BookResponseDto:返回给客户端的完整数据。
  • BookCreateDto:创建时客户端需要提交的数据。
  • BookUpdateDto:更新时客户端需要提交的数据。

为什么要拆成三个?因为创建和更新场景下客户端不需要提交IdCreatedAt;返回场景下才需要。如果所有字段都用一个类,接口文档会变得含糊,客户端也不知道哪些字段可以不传。

文件路径:Dtos/BookDtos.cs

namespace BookStore.Api.Dtos; public record BookResponseDto( int Id, string Title, string? Author, string? ISBN, DateTime PublishDate, decimal Price, DateTime CreatedAt); public record BookCreateDto( string Title, string? Author, string? ISBN, DateTime PublishDate, decimal Price); public record BookUpdateDto( string Title, string? Author, string? ISBN, DateTime PublishDate, decimal Price);

这里使用 C# 的record类型,它天然适合只读数据传输对象,支持值相等比较,代码也更简洁。如果你需要传统 class 风格,也可以换成普通类,但record在 DTO 场景下是更现代、更推荐的选择。

6. 实现 Controller 完成 CRUD

6.1 构造注入 DbContext

文件路径:Controllers/BooksController.cs

在 Controller 的构造函数中注入BookStoreContext。这里采用构造注入,是 ASP.NET Core 依赖注入的标准做法。严格来说,业务复杂后不应直接注入 DbContext 到 Controller,而应该经过 Service/Repository 层。但为了让示例聚焦在“Controller + EF Core + SQL Server + DTO”的链路本身,我暂时直接把 DbContext 注入 Controller,并在第 9 节讨论如何进一步拆分。

using BookStore.Api.Data; using BookStore.Api.Dtos; using BookStore.Api.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace BookStore.Api.Controllers; [ApiController] [Route("api/[controller]")] public class BooksController : ControllerBase { private readonly BookStoreContext _context; public BooksController(BookStoreContext context) { _context = context; } }

6.2 查询列表

[HttpGet] public async Task<ActionResult<IEnumerable<BookResponseDto>>> GetBooks() { var books = await _context.Books .AsNoTracking() .OrderByDescending(b => b.CreatedAt) .Select(b => new BookResponseDto( b.Id, b.Title, b.Author, b.ISBN, b.PublishDate, b.Price, b.CreatedAt)) .ToListAsync(); return Ok(books); }

AsNoTracking()告诉 EF Core 不需要跟踪实体状态,因为查询结果只是只读展示,这样可以减少状态管理开销。使用Select直接把实体映射到 DTO,而不是先查出实体再手动转 DTO,SQL 语句也会只查询 DTO 中出现的字段,效率更高。

6.3 查询单个

[HttpGet("{id:int}")] public async Task<ActionResult<BookResponseDto>> GetBook(int id) { var book = await _context.Books .AsNoTracking() .Where(b => b.Id == id) .Select(b => new BookResponseDto( b.Id, b.Title, b.Author, b.ISBN, b.PublishDate, b.Price, b.CreatedAt)) .FirstOrDefaultAsync(); if (book is null) { return NotFound(); } return Ok(book); }

这里的关键是路由约束{id:int}。它保证api/books/abc这类请求直接在路由匹配阶段失败,不会进入真正的方法体,减少不必要的参数校验代码。

6.4 创建

[HttpPost] public async Task<ActionResult<BookResponseDto>> CreateBook(BookCreateDto dto) { var book = new Book { Title = dto.Title, Author = dto.Author, ISBN = dto.ISBN, PublishDate = dto.PublishDate, Price = dto.Price }; _context.Books.Add(book); await _context.SaveChangesAsync(); var response = new BookResponseDto( book.Id, book.Title, book.Author, book.ISBN, book.PublishDate, book.Price, book.CreatedAt); return CreatedAtAction(nameof(GetBook), new { id = book.Id }, response); }

创建成功返回 201 Created,同时通过CreatedAtAction在响应头中携带新资源的访问 URI。这是 RESTful API 的规范行为,很多刚接触 Web API 的开发者会直接返回 200 OK,虽然能用,但不够标准。

SaveChangesAsync执行后,EF Core 会把数据库生成的自增主键回写到book.Id,所以后续构造response时可以直接使用。

6.5 更新

[HttpPut("{id:int}")] public async Task<IActionResult> UpdateBook(int id, BookUpdateDto dto) { var book = await _context.Books.FindAsync(id); if (book is null) { return NotFound(); } book.Title = dto.Title; book.Author = dto.Author; book.ISBN = dto.ISBN; book.PublishDate = dto.PublishDate; book.Price = dto.Price; await _context.SaveChangesAsync(); return NoContent(); }

更新接口使用HttpPut,语义是“全量替换”。这里FindAsync查出来的实体会被 DbContext 跟踪,修改属性后调用SaveChangesAsync,EF Core 会自动生成 UPDATE 语句。更新成功返回 204 No Content,表示没有返回体需要客户端解析。

注意:PUT的语义是全量更新,所以客户端必须提交所有必填字段。如果只想更新个别字段,应使用PATCH,需要额外处理部分更新,本文不再展开。

6.6 删除

[HttpDelete("{id:int}")] public async Task<IActionResult> DeleteBook(int id) { var book = await _context.Books.FindAsync(id); if (book is null) { return NotFound(); } _context.Books.Remove(book); await _context.SaveChangesAsync(); return NoContent(); }

删除同样是先判断存在性,删除成功返回 204。对于真实业务,通常不建议物理删除业务数据,而应采用软删除(加入IsDeleted字段,查询时自动过滤)。这个建议会在第 9 节详细展开。

到这里,Controller 的 CRUD 已经完整实现。六个方法覆盖了列表、详情、创建、更新、删除五个操作,响应状态码也符合 REST 习惯:200、201、204、404。

7. 迁移、运行与接口验证

7.1 创建迁移

在项目目录下执行:

dotnet ef migrations add InitialCreate

正常执行后,项目里会生成Migrations目录,包含一个时间戳命名的迁移文件。迁移文件记录的是从“空数据库”到“当前模型”的增量变化,它可以被提交到代码仓库,团队成员通过dotnet ef database update同步到本地数据库。

如果执行失败,最常见的原因是dotnet ef没找到项目中的 DbContext。确认BookStoreContext已经通过AddDbContext注册到Program.cs,并且项目可以正常编译。

7.2 更新数据库

dotnet ef database update

该命令会根据迁移记录创建数据库和表。执行完成后,可以打开 SSMS(SQL Server Management Studio)或使用命令行验证:

sqlcmd -S "(localdb)\\MSSQLLocalDB" -d BookStoreDb -Q "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"

如果能看到Books表,说明数据库已经生成成功。

7.3 启动 API

dotnet run

默认端口由launchSettings.json决定,通常是http://localhost:5xxx。启动后,在浏览器打开http://localhost:5xxx/swagger,可以看到 Swagger UI,里面列出了 Books 的五个接口。

7.4 用 curl 验证接口

用 curl 发送一个创建请求:

curl -X POST "http://localhost:5xxx/api/books" \ -H "Content-Type: application/json" \ -d '{ "title": "深入理解ASP.NET Core", "author": "张三", "isbn": "978-7-xxx-xxxxx-x", "publishDate": "2025-01-01T00:00:00", "price": 99.00 }'

预期输出:

{ "id": 1, "title": "深入理解ASP.NET Core", "author": "张三", "isbn": "978-7-xxx-xxxxx-x", "publishDate": "2025-01-01T00:00:00", "price": 99.0, "createdAt": "2025-01-01T00:00:00" }

再验证列表接口:

curl "http://localhost:5xxx/api/books"

返回 JSON 数组,说明整个链路已经打通:HTTP 请求进入 Controller,DTO 被绑定,EF Core 操作 SQL Server 完成持久化,结果再次映射为 DTO 返回客户端。

如果请求返回 500 错误,先看控制台日志,重点排查数据库连接串是否写对、SQL Server 实例是否启动。这是最常见的两个原因。

8. 常见问题与排查思路

从评论区和网上搜索热度来看,SQL Server 连接问题出现频率最高。下面整理几组典型问题,供你对照排查。

问题现象可能原因排查方式解决方案
运行迁移时报“wait on the database engine recovery handle failed”SQL Server 服务启动失败或数据库文件损坏查看 Windows 事件查看器中的 SQL Server 日志检查服务账号权限,尝试修复或重装 SQL Server 实例
连接字符串报 Named Pipes Provider 错误(08001)客户端协议被禁用或实例名错误打开 SQL Server 配置管理器,确认 TCP/IP 和 Named Pipes 已启用启用相应协议,或用 sqlcmd 测试实例是否可连
本机提示未找到 LocalDB 实例未安装 LocalDB 或 SQL Server Express执行sqllocaldb info查看实例列表安装 SQL Server Express/Developer Edition,或改用完整实例名
调用接口返回 500 Internal Server Error连接字符串错误、数据库未迁移、模型字段约束冲突查看 API 控制台异常堆栈先确认数据库可连通,再执行dotnet ef database update
POST 请求返回 400 且模型校验失败DTO 必填字段未传或类型不匹配查看响应体中的 ModelState 错误按错误提示补充字段,确认 JSON 属性名与 DTO 一致
执行dotnet ef提示找不到命令dotnet-ef 工具未安装或 PATH 未生效执行dotnet tool list -g执行dotnet tool install --global dotnet-ef后重开终端

排查 SQL Server 连接问题时,最有效的方法是先用sqlcmd或 SSMS 确认数据库实例能连上,再回到 API 代码中找问题。很多“程序连不上数据库”的案例,最后发现是服务没启动、端口没开放、实例名写错,而不是代码问题。

如果你使用的 SQL Server 版本较老,例如安装在 Windows 2016 等旧系统上,还需要额外关注 TLS 协议和驱动兼容性。微软官方建议尽量使用受支持的 SQL Server 版本,新的应用项目优先选择 SQL Server 2022 或更新的 Developer Edition。

9. 最佳实践与工程建议

项目能跑通只是第一步。如果要把这套骨架放进真实项目里长期维护,下面这些建议值得认真考虑。

9.1 DTO 不要直接复用实体

不要图省事直接在 Controller 里返回实体对象,也不要让 DTO 继承实体。DTO 应该是独立的、只服务于 API 边界的类型。数据库加字段、改字段名时,DTO 和映射逻辑能为你挡住大部分破坏性变化。

9.2 所有数据库操作都使用异步方法

EF Core 提供的ToListAsyncFirstOrDefaultAsyncSaveChangesAsync等异步方法,能在数据库等待期间释放线程,提升 API 在高并发下的吞吐能力。Controller Action 本身也应该是async Taskasync Task<ActionResult<T>>

9.3 查询列表时尽量使用分页

真实项目中数据量会逐步增长,直接返回全表列表早晚要出问题。及时引入分页参数:

[HttpGet] public async Task<ActionResult<IEnumerable<BookResponseDto>>> GetBooks( [FromQuery] int page = 1, [FromQuery] int pageSize = 20) { var books = await _context.Books .AsNoTracking() .OrderByDescending(b => b.CreatedAt) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(b => new BookResponseDto( b.Id, b.Title, b.Author, b.ISBN, b.PublishDate, b.Price, b.CreatedAt)) .ToListAsync(); return Ok(books); }

分页参数要设置上限,避免客户端传一个巨大的pageSize把数据库拖垮。关注接口性能的团队,还可以在Select前加入Where条件,并在数据库层建立合适索引。

9.4 生产环境必须处理安全边界

  • 不要使用sa账号连接数据库,为应用创建独立账号并只授予所需库的最小权限。
  • 连接字符串不要硬编码在代码里,使用环境变量、用户机密或配置中心。
  • 如果 API 需要认证授权,在Program.cs中正确配置AddAuthenticationAddAuthorization,并且使用 HTTPS。
  • 不要信任前端传入的任意字段。BookCreateDto中未声明的属性不会被绑定,但 Controller 里仍要使用数据注解验证必填字段和字段长度。

9.5 不要为了“分层”而分层

很多文章会推荐 Controller -> Service -> Repository 三层架构。但若项目规模不大,引入过多抽象层只会增加阅读成本。合理的做法是:团队在 5 个接口以内时先保持简单;等出现业务逻辑复用或事务跨多个实体时,再提取 Service 层。避免在刚起步时就写出一堆没有任何业务逻辑的 Service 和 Repository。

9.6 使用迁移管理数据库变更

每次模型变更,都通过dotnet ef migrations add生成迁移,并通过dotnet ef database update应用到目标数据库。生产环境的数据库变更需要走审核流程,确保迁移脚本先备份再执行,有条件的话先恢复到预发布环境验证。

小结与后续方向

到这里,一条从零到一的 .NET 10 Web API 构建路径已经完整走完:创建项目、配置 EF Core 和 SQL Server、定义实体和 DbContext、设计 DTO、实现 Controller 的 CRUD、执行迁移、通过 Swagger/curl 验证接口,以及常见的 SQL Server 连接问题排查思路。

这套骨架的结构并不复杂,难的是理解每个组件为什么放在这个位置:DTO 是 API 的合同,EF Core 是数据访问的抽象,SQL Server 是持久化的担当,Controller 是 HTTP 的门面。理解这一点后,后续无论项目规模如何膨胀,你都能清楚地知道某个改动应该落在哪一层。

下一步你可以继续探索的方向包括:使用 AutoMapper 优化实体到 DTO 的映射、引入 Service 层拆解复杂业务、为 Controller 编写集成测试、加入认证授权、使用 FluentValidation 替代默认数据注解校验。建议先把本文的代码跑通一遍,再按自己的业务场景填字段、加逻辑。

如果把本文收藏起来照着敲一遍,你会发现自己已经具备从零搭建完整 Web API 骨架的能力。这一套组合,值得成为你技术栈里最常用的选项之一。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 7:29:34

免费网盘直链解析工具LinkSwift:9大网盘下载加速的完整指南

免费网盘直链解析工具LinkSwift&#xff1a;9大网盘下载加速的完整指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 /…

作者头像 李华
网站建设 2026/8/27 7:26:39

CS+SAR雷达成像原理与Matlab实现详解

简介&#xff1a;压缩感知&#xff08;CS&#xff09;是一种突破奈奎斯特采样定理的信号重建理论&#xff0c;其核心在于利用信号在特定变换域&#xff08;如小波、傅里叶&#xff09;的稀疏性&#xff0c;通过欠采样观测和L1范数优化实现高保真重构。在合成孔径雷达&#xff0…

作者头像 李华
网站建设 2026/8/27 7:26:19

家庭实验室服务菜单:Debian GNOME 中用 .desktop 文件打造统一入口

先说明一个真实场景&#xff1a;家庭实验室里的服务越来越多&#xff0c;路由器后台开端口、NAS 跑 Docker、一台 Debian 主机上挂着十几个自建服务&#xff0c;每个服务都对应一个 IP 加端口。浏览器书签栏越塞越满&#xff0c;真正要找某个服务时反而找不到。Kinjo 这个项目名…

作者头像 李华
网站建设 2026/8/27 7:24:31

Agent记忆系统与数据分支:oGMemory如何解决多轮对话的长期记忆难题

“对话一长&#xff0c;Agent 就开始‘失忆’&#xff1b;换个新 session&#xff0c;上一轮结论全丢&#xff1b;想让不同方向的实验互不干扰&#xff0c;只能靠复制环境硬扛。”如果你最近在做 Agent 应用&#xff0c;大概率已经撞上了这个问题。记忆系统正在成为 Agent 工程…

作者头像 李华
网站建设 2026/8/27 7:23:55

嵌入式SSD长寿命应用:NAND数据保持力与选型实战指南

1. 为什么"写入也不多"的SSD反而先坏了&#xff1a;一次现场故障给我的教训我前几年配合过一个电力行业的项目&#xff0c;设备装在现场&#xff0c;平时只记录一些状态数据&#xff0c;一天撑死写几十MB。按这个写入量估算&#xff0c;SSD用二十年都绰绰有余。结果设…

作者头像 李华