- 准备工作:搭建开发环境和数据库。
- 核心概念:了解 ADO.NET 和 EF Core 这两种主流的数据访问方式。
- 实战演练:通过一个完整的“待办事项列表”项目,分别演示使用 ADO.NET 和 Entity Framework Core (EF Core) 进行数据库操作。
- 进阶主题:介绍一些高级概念和最佳实践。
第 1 部分:准备工作
在开始之前,请确保您已安装以下软件:

- .NET SDK:推荐安装最新的长期支持版本,.NET 6, 7, 8。
- IDE (集成开发环境):
- Visual Studio 2025 (推荐,社区版免费): 提供了强大的 ASP.NET 开发支持。
- Visual Studio Code: 轻量级编辑器,配合 C# Dev Kit 扩展也能很好地进行开发。
- 数据库管理系统:
- SQL Server Express LocalDB:Visual Studio 安装时会自动包含一个轻量级的 SQL Server 版本,非常适合初学者。
- SQL Server:完整的数据库服务器。
- SQLite:一个轻量级的、文件式的数据库,无需安装服务器,非常适合学习和测试。
- PostgreSQL / MySQL:其他流行的开源数据库选择。
第 2 部分:核心概念 - 两种数据访问方式
在 ASP.NET 中,与数据库交互主要有两种主流方式:ADO.NET 和 Entity Framework Core (EF Core)。
1 ADO.NET (传统方式)
ADO.NET 是 .NET 中用于与数据源(如数据库)进行交互的低级 API,它提供了对数据库连接、命令、数据读取器等的直接控制。
-
特点:
- 性能高:直接操作数据库,开销小。
- 控制力强:开发者可以精确控制 SQL 语句和数据库操作。
- 代码繁琐:需要编写大量样板代码来连接、打开、关闭连接,并手动处理数据映射。
- SQL 注入风险:如果直接拼接 SQL 字符串,容易受到 SQL 注入攻击(通常使用参数化查询来避免)。
-
核心组件:
(图片来源网络,侵删)SqlConnection:建立与 SQL Server 的连接。SqlCommand:执行 SQL 命令(查询、插入、更新、删除)。SqlDataReader:只进、只读的数据流,用于读取查询结果。DataSet/DataTable:内存中的数据缓存,用于离线操作和复杂关系处理。
2 Entity Framework Core (现代方式)
Entity Framework Core (EF Core) 是一个现代的对象关系映射器,它允许您使用 .NET 对象(称为“实体”)来操作数据库,而无需编写大量的 SQL 代码。
-
特点:
- 开发效率高:将数据库表映射为 C# 类(实体),通过操作这些类来完成数据操作。
- 代码简洁:减少了大量的样板代码。
- 数据库无关性:可以通过更换数据库提供程序,轻松地将应用从一种数据库(如 SQL Server)迁移到另一种(如 PostgreSQL)。
- 功能强大:支持 LINQ 查询、数据迁移、变更追踪等高级功能。
- 轻微性能开销:相比原生 ADO.NET,存在一定的抽象层开销,但在大多数应用中,这个开销可以忽略不计。
-
核心概念:
- DbContext (上下文):这是 EF Core 的核心,它负责与数据库的交互,并跟踪实体的状态。
- Entity (实体):一个普通的 C# 类,通常对应数据库中的一张表。
- DbSet (数据集):在
DbContext中定义的DbSet<T>属性,代表数据库中的一张表。 - Migration (迁移):一种机制,用于将您的实体模型变更(如添加新表、新字段)同步到数据库结构。
第 3 部分:实战演练 - 创建一个待办事项应用
我们将创建一个简单的 ASP.NET Core MVC 应用,实现“添加”、“查看”、“编辑”和“删除”待办事项的功能。
步骤 1:创建项目
- 打开 Visual Studio 2025。
- 选择“创建新项目”。
- 搜索并选择 “ASP.NET Core Web 应用” 模板。
- 点击“下一步”,命名为
TodoApp。 - 在“其他信息”页面中:
- 框架:选择最新的 .NET 版本 (如 .NET 8.0)。
- 身份验证类型:选择“无”。
- 配置为 API:取消勾选。
- 勾选“为 HTTPS 配置”。
- 点击“创建”。
步骤 2:选择数据访问方式并实现
我们分别用 ADO.NET 和 EF Core 来实现数据访问层。
方案 A:使用 ADO.NET
这种方式更接近“裸”的数据库操作,能让你理解底层发生了什么。
创建数据库
- 打开 SQL Server Object Explorer (在 Visual Studio 的 "视图" -> "SQL Server Object Explorer")。
- 连接到
LocalDB(通常是(localdb)\MSSQLLocalDB)。 - 右键点击 "数据库",选择 "添加新数据库",命名为
TodoDb。 - 在
TodoDb上右键,选择 "新建查询",然后执行以下 SQL 语句来创建TodoItems表:
CREATE TABLE TodoItems (
Id INT PRIMARY KEY IDENTITY(1,1),NVARCHAR(100) NOT NULL,
IsDone BIT NOT NULL DEFAULT 0,
DueDate DATETIME NULL
);
创建数据模型
在 Models 文件夹中,创建 TodoItem.cs 文件:
// Models/TodoItem.cs
namespace TodoApp.Models
{
public class TodoItem
{
public int Id { get; set; }
public string Title { get; set; }
public bool IsDone { get; set; }
public DateTime? DueDate { get; set; }
}
}
创建数据库服务
为了管理数据库连接,我们创建一个服务类。
- 在
Services文件夹(如果没有就创建一个)中,创建TodoDbService.cs:
// Services/TodoDbService.cs
using System.Data;
using System.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using TodoApp.Models;
namespace TodoApp.Services
{
public class TodoDbService
{
private readonly IConfiguration _configuration;
private readonly string _connectionString;
public TodoDbService(IConfiguration configuration)
{
_configuration = configuration;
// 从 appsettings.json 读取连接字符串
_connectionString = _configuration.GetConnectionString("DefaultConnection");
}
// 获取所有待办事项
public List<TodoItem> GetAll()
{
var todos = new List<TodoItem>();
using (var connection = new SqlConnection(_connectionString))
{
var command = new SqlCommand("SELECT Id, Title, IsDone, DueDate FROM TodoItems", connection);
connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
todos.Add(new TodoItem
{
Id = reader.GetInt32(0),
Title = reader.GetString(1),
IsDone = reader.GetBoolean(2),
DueDate = reader.IsDBNull(3) ? (DateTime?)null : reader.GetDateTime(3)
});
}
}
}
return todos;
}
// 添加一个新的待办事项
public void Add(TodoItem item)
{
using (var connection = new SqlConnection(_connectionString))
{
var command = new SqlCommand("INSERT INTO TodoItems (Title, IsDone, DueDate) VALUES (@Title, @IsDone, @DueDate)", connection);
command.Parameters.AddWithValue("@Title", item.Title);
command.Parameters.AddWithValue("@IsDone", item.IsDone);
command.Parameters.AddWithValue("@DueDate", (object)item.DueDate ?? DBNull.Value);
connection.Open();
command.ExecuteNonQuery();
}
}
// 更新待办事项
public void Update(TodoItem item)
{
using (var connection = new SqlConnection(_connectionString))
{
var command = new SqlCommand("UPDATE TodoItems SET Title = @Title, IsDone = @IsDone, DueDate = @DueDate WHERE Id = @Id", connection);
command.Parameters.AddWithValue("@Id", item.Id);
command.Parameters.AddWithValue("@Title", item.Title);
command.Parameters.AddWithValue("@IsDone", item.IsDone);
command.Parameters.AddWithValue("@DueDate", (object)item.DueDate ?? DBNull.Value);
connection.Open();
command.ExecuteNonQuery();
}
}
// 删除待办事项
public void Delete(int id)
{
using (var connection = new SqlConnection(_connectionString))
{
var command = new SqlCommand("DELETE FROM TodoItems WHERE Id = @Id", connection);
command.Parameters.AddWithValue("@Id", id);
connection.Open();
command.ExecuteNonQuery();
}
}
}
}
配置连接字符串
打开 appsettings.json 文件,添加连接字符串:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=TodoDb;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": { ... },
"AllowedHosts": "*"
}
注册服务和控制器
- 注册服务:在
Program.cs中,将我们的服务注册到依赖注入容器中。
// Program.cs using TodoApp.Services; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); // 注册 TodoDbService builder.Services.AddScoped<TodoDbService>(); var app = builder.Build(); // ... 其余代码不变
- 创建控制器:在
Controllers文件夹中,右键添加 -> "控制器" -> "MVC 控制器 - 空",命名为TodoController。
// Controllers/TodoController.cs
using Microsoft.AspNetCore.Mvc;
using TodoApp.Models;
using TodoApp.Services;
namespace TodoApp.Controllers
{
public class TodoController : Controller
{
private readonly TodoDbService _todoService;
public TodoController(TodoDbService todoService)
{
_todoService = todoService;
}
// GET: Todo
public IActionResult Index()
{
var todos = _todoService.GetAll();
return View(todos);
}
// GET: Todo/Create
public IActionResult Create()
{
return View();
}
// POST: Todo/Create
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(TodoItem item)
{
if (ModelState.IsValid)
{
_todoService.Add(item);
return RedirectToAction(nameof(Index));
}
return View(item);
}
// GET: Todo/Edit/5
public IActionResult Edit(int id)
{
var item = _todoService.GetAll().FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return View(item);
}
// POST: Todo/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, TodoItem item)
{
if (id != item.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
_todoService.Update(item);
return RedirectToAction(nameof(Index));
}
return View(item);
}
// GET: Todo/Delete/5
public IActionResult Delete(int id)
{
var item = _todoService.GetAll().FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound();
}
return View(item);
}
// POST: Todo/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public IActionResult DeleteConfirmed(int id)
{
_todoService.Delete(id);
return RedirectToAction(nameof(Index));
}
}
}
创建视图
- 在
TodoController的每个View()方法上右键,选择“添加视图”。 - 视图类型:选择
List(Index),Create,Edit,Delete。 - 模型类:选择
TodoApp.Models.TodoItem。 - 数据上下文类:留空。
- 勾选“使用布局页面”。
- 生成所有视图后,运行项目,访问
/Todo即可看到效果。
方案 B:使用 Entity Framework Core (推荐)
这是现代 .NET 开发的标准做法,效率更高,代码更优雅。
安装 EF Core 包
在 Visual Studio 的“解决方案资源管理器”中,右键点击项目 -> “管理 NuGet 程序包”。 搜索并安装以下两个包:
Microsoft.EntityFrameworkCore.SqlServerMicrosoft.EntityFrameworkCore.Tools
创建数据模型
与 ADO.NET 方案中的 Models/TodoItem.cs 完全相同。
创建 DbContext
DbContext 是 EF Core 的核心。
- 在
Data文件夹(如果没有就创建一个)中,创建TodoDbContext.cs:
// Data/TodoDbContext.cs
using Microsoft.EntityFrameworkCore;
using TodoApp.Models;
namespace TodoApp.Data
{
public class TodoDbContext : DbContext
{
public TodoDbContext(DbContextOptions<TodoDbContext> options) : base(options)
{
}
// DbSet 代表数据库中的一张表
public DbSet<TodoItem> TodoItems { get; set; }
}
}
配置 DbContext 和连接字符串
-
连接字符串:与 ADO.NET 方案中
appsettings.json的配置 完全相同。 -
注册 DbContext:在
Program.cs中,将DbContext注册到依赖注入容器中。
// Program.cs
using Microsoft.EntityFrameworkCore;
using TodoApp.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
// 配置和注册 TodoDbContext
builder.Services.AddDbContext<TodoDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
// ... 其余代码不变
创建数据库迁移
这是 EF Core 的强大功能,它会根据你的模型自动创建或更新数据库结构。
-
在 Visual Studio 的“程序包管理器控制台”(Package Manager Console) 中 (工具 -> NuGet 包管理器 -> 程序包管理器控制台)。
-
执行以下命令:
Add-Migration InitialCreate
InitialCreate是迁移名称,你可以自定义。- 此命令会创建一个包含数据库创建脚本的 Migrations 文件夹。
-
再次执行以下命令,将迁移应用到数据库:
Update-Database
- 你的
TodoDb数据库中会自动出现TodoItems表,甚至包含一个__EFMigrationsHistory表来记录迁移历史。
- 你的
创建仓储模式 (Repository Pattern - 可选但推荐)
为了解耦控制器和数据访问逻辑,我们创建一个仓储。
- 在
Repositories文件夹中,创建ITodoRepository.cs(接口) 和TodoRepository.cs(实现)。
// Repositories/ITodoRepository.cs
using TodoApp.Models;
namespace TodoApp.Repositories
{
public interface ITodoRepository
{
IEnumerable<TodoItem> GetAll();
TodoItem GetById(int id);
void Add(TodoItem item);
void Update(TodoItem item);
void Delete(int id);
}
}
// Repositories/TodoRepository.cs
using Microsoft.EntityFrameworkCore;
using TodoApp.Data;
using TodoApp.Models;
namespace TodoApp.Repositories
{
public class TodoRepository : ITodoRepository
{
private readonly TodoDbContext _context;
public TodoRepository(TodoDbContext context)
{
_context = context;
}
public void Add(TodoItem item)
{
_context.TodoItems.Add(item);
_context.SaveChanges();
}
public void Delete(int id)
{
var item = _context.TodoItems.Find(id);
if (item != null)
{
_context.TodoItems.Remove(item);
_context.SaveChanges();
}
}
public IEnumerable<TodoItem> GetAll()
{
return _context.TodoItems.ToList();
}
public TodoItem GetById(int id)
{
return _context.TodoItems.Find(id);
}
public void Update(TodoItem item)
{
_context.Entry(item).State = EntityState.Modified;
_context.SaveChanges();
}
}
}
注册仓储和控制器
- 注册仓储:在
Program.cs中注册仓储接口和实现。
// Program.cs // ... 在 AddDbContext 之后添加 builder.Services.AddScoped<ITodoRepository, TodoRepository>();
- 创建控制器:创建一个新的
TodoController(或者修改旧的),这次使用ITodoRepository。
// Controllers/TodoController.cs (EF Core 版本)
using Microsoft.AspNetCore.Mvc;
using TodoApp.Models;
using TodoApp.Repositories;
namespace TodoApp.Controllers
{
public class TodoController : Controller
{
private readonly ITodoRepository _repository;
public TodoController(ITodoRepository repository)
{
_repository = repository;
}
public IActionResult Index()
{
var todos = _repository.GetAll();
return View(todos);
}
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(TodoItem item)
{
if (ModelState.IsValid)
{
_repository.Add(item);
return RedirectToAction(nameof(Index));
}
return View(item);
}
public IActionResult Edit(int id)
{
var item = _repository.GetById(id);
if (item == null)
{
return NotFound();
}
return View(item);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, TodoItem item)
{
if (id != item.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
_repository.Update(item);
return RedirectToAction(nameof(Index));
}
return View(item);
}
public IActionResult Delete(int id)
{
var item = _repository.GetById(id);
if (item == null)
{
return NotFound();
}
return View(item);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public IActionResult DeleteConfirmed(int id)
{
_repository.Delete(id);
return RedirectToAction(nameof(Index));
}
}
}
创建视图
视图的代码与 ADO.NET 方案中的视图 完全相同!因为它们操作的都是 TodoItem 模型。
运行项目,你会发现功能与 ADO.NET 版本一模一样,但代码量更少,结构更清晰。
第 4 部分:进阶主题与最佳实践
-
异步操作:在 Web 应用中,所有 I/O 操作(如数据库访问)都应该是异步的,以避免阻塞线程,提高并发性能。
- 在 EF Core 中,将
SaveChanges()改为SaveChangesAsync()。 - 在 ADO.NET 中,使用
ExecuteReaderAsync(),ExecuteNonQueryAsync()等。 - 在控制器中,将
public IActionResult Index()改为public async Task<IActionResult> Index()。
- 在 EF Core 中,将
-
依赖注入:我们已经使用了依赖注入,这是现代 .NET 应用的基石,它使代码更易于测试和维护。
-
仓储模式 vs. 直接在 Controller 中使用 DbContext:
- 对于小型应用,可以直接在 Controller 中注入
DbContext。 - 对于中大型应用,仓储模式可以更好地解耦业务逻辑和数据访问逻辑,使代码结构更清晰。
- 对于小型应用,可以直接在 Controller 中注入
-
DTO (Data Transfer Objects):不要直接将你的
Entity模型暴露给 API 或视图,创建专门的 DTO 类,只包含需要传输的字段,这可以防止意外数据泄露,并允许你灵活地定义 API 契约。 -
单元测试:使用仓储模式和依赖注入,可以轻松地为你的服务层和控制器编写单元测试,而无需连接真实的数据库。
| 特性 | ADO.NET | Entity Framework Core |
|---|---|---|
| 抽象级别 | 低级,直接控制 | 高级,ORM 封装 |
| 性能 | 最高,无额外开销 | 略低,但通常可接受 |
| 开发效率 | 较低,代码繁琐 | 非常高,代码简洁 |
| 控制力 | 完全控制 SQL 和连接 | 通过 LINQ 和配置控制 |
| 数据库迁移 | 手动编写和管理 | 自动化,非常方便 |
| 适用场景 | 性能要求极高的场景、复杂存储过程、遗留系统维护 | 绝大多数现代 Web 应用、快速开发、跨数据库应用 |
给初学者的建议:直接从 Entity Framework Core 开始学习,它能让你更专注于业务逻辑的实现,而不是底层数据库连接的细节,当你对 .NET 和数据库操作有了更深入的理解后,再去了解 ADO.NET 会更有帮助。
