.NET Entity Framework (EF) Core 全方位教程
目录
- 什么是 EF Core?为什么选择它?
- 核心概念:EF Core 是如何工作的?
- 环境准备:安装与配置
- 第一步:创建你的第一个 EF Core 项目
- [创建数据库模型]
- [创建 DbContext]
- [配置数据库连接]
- [创建并迁移数据库]
- CRUD 操作详解
- [创建 - Add() 和 SaveChanges()]
- [读取 - 查询:LINQ to Entities]
- [更新 - 跟踪、修改和 SaveChanges()]
- [删除 - Remove() 和 SaveChanges()]
- 进阶主题
- [数据迁移 - 管理数据库架构]
- [关系配置 - 一对一、一对多、多对多]
- [数据注解 vs. Fluent API]
- [处理并发冲突]
- [性能优化:延迟加载 vs. 预加载 vs. 显式加载]
- 最佳实践与建议
- 学习资源
1. 什么是 EF Core?为什么选择它?
Entity Framework (EF) Core 是微软官方的开源、跨平台、轻量级的对象关系映射器,它是一个数据访问技术,让你能够使用 .NET 对象来操作数据库,而无需编写大量的数据访问代码(如 SQL 语句)。
核心思想:
- 映射关系:将数据库中的表映射到 C# 中的类(称为“实体”或“模型”)。
- 对象操作:通过操作这些 C# 对象,EF Core 会自动将它们转换为相应的 SQL 语句,并与数据库进行交互。
为什么选择 EF Core?
- 提高开发效率:无需手写繁琐、易错的 SQL 代码,专注于业务逻辑。
- 数据库抽象:你的代码与具体的数据库(SQL Server, PostgreSQL, SQLite, MySQL 等)解耦,更换数据库提供商通常只需修改一行配置。
- 强大的查询能力:使用 LINQ (Language Integrated Query) 进行类型安全的查询,编译器会帮你检查错误。
- 变更追踪:EF Core 会自动跟踪你从数据库中获取的实体对象,当你修改这些对象并调用
SaveChanges()时,它会自动生成正确的UPDATE语句。 - 迁移功能:可以方便地根据 C# 模型的变更来更新数据库架构,实现了代码优先的开发模式。
- 开源与社区支持:活跃的开源社区,持续更新和维护。
2. 核心概念:EF Core 是如何工作的?
理解以下几个核心概念至关重要:
-
DbContext (上下文):
- 这是 EF Core 的核心类,是应用程序与数据库之间的“桥梁”。
- 它负责:
- 连接到数据库。
- 跟踪实体的变化。
- 将实体保存到数据库中。
- 从数据库查询数据。
- 一个
DbContext对应一个数据库会话。
-
实体:
- 一个简单的 C# 类(POCO - Plain Old CLR Object),通常对应数据库中的一张表。
- 类的属性对应表中的列。
public class User { public int Id { get; set; } public string Name { get; set; } }
-
DbSet:
- 在
DbContext中,一个DbSet<T>属性代表数据库中的一张表。 T是实体的类型。public DbSet<User> Users { get; set; }代表Users表。
- 在
-
迁移:
- 一个命令行工具,用于根据你的模型变化(添加/修改实体、属性)来创建、应用或回滚数据库架构的变更。
- 它会生成一系列 C# 代码(迁移脚本),这些脚本描述了数据库结构的变更,然后执行它们。
3. 环境准备:安装与配置
前提条件:
- 安装 .NET 6 SDK 或更高版本。
- 一个代码编辑器,推荐 Visual Studio 2025 或 Visual Studio Code。
安装 EF Core 工具: 在项目目录下,通过 NuGet 包管理器控制台或终端安装 EF Core 的命令行工具。
# 安装用于创建和管理迁移的命令行工具 dotnet tool install --global dotnet-ef # 安装设计时包,它允许 EF Core 在没有运行应用程序的情况下读取模型 dotnet add package Microsoft.EntityFrameworkCore.Design
安装数据库提供程序: 根据你使用的数据库,安装对应的包。
- SQL Server:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer - SQLite:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite - PostgreSQL:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL - MySQL:
dotnet add package Pomelo.EntityFrameworkCore.MySql
4. 第一步:创建你的第一个 EF Core 项目
我们将创建一个控制台应用程序,并使用 SQLite 数据库。
创建项目
dotnet new console -n EfCoreGettingStarted cd EfCoreGettingStarted
安装必要的包
# EF Core 核心包 dotnet add package Microsoft.EntityFrameworkCore # SQLite 数据库提供程序 dotnet add package Microsoft.EntityFrameworkCore.Sqlite # 设计时工具 dotnet add package Microsoft.EntityFrameworkCore.Design
创建数据库模型
创建一个 Models 文件夹,并在其中添加 Blog.cs 文件。
// Models/Blog.cs
namespace EfCoreGettingStarted.Models
{
public class Blog
{
public int Id { get; set; }
public string Url { get; set; }
}
}
创建 DbContext
创建一个 Data 文件夹,并在其中添加 BloggingContext.cs 文件。
// Data/BloggingContext.cs
using Microsoft.EntityFrameworkCore;
using EfCoreGettingStarted.Models;
namespace EfCoreGettingStarted.Data
{
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options) : base(options)
{
}
public DbSet<Blog> Blogs { get; set; }
}
}
配置数据库连接
打开 Program.cs,配置 DbContext 并连接到数据库。
// Program.cs
using Microsoft.EntityFrameworkCore;
using EfCoreGettingStarted.Data;
var builder = WebApplication.CreateBuilder(args);
// 1. 获取配置
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
// 2. 注册 DbContext 到依赖注入容器
// 使用 SQLite 数据库提供程序
builder.Services.AddDbContext<BloggingContext>(options =>
options.UseSqlite(connectionString));
var app = builder.Build();
// ... (后续代码)
为了让配置生效,在 appsettings.json 中添加连接字符串:
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=blogs.db"
}
}
创建并迁移数据库 让我们使用 EF Core 工具来创建数据库。
- 在项目根目录打开终端。
- 运行以下命令,创建一个初始迁移:
dotnet ef migrations add InitialCreate
此命令会生成一个 Migrations 文件夹,里面包含描述数据库结构的 C# 代码。
- 然后应用这个迁移,创建数据库:
dotnet ef database update
你的项目目录下会多出一个
blogs.db文件,这就是你的 SQLite 数据库。
5. CRUD 操作详解
让我们在 Program.cs 中编写代码来执行增删改查操作。
完整 Program.cs 示例
using Microsoft.EntityFrameworkCore;
using EfCoreGettingStarted.Data;
using EfCoreGettingStarted.Models;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<BloggingContext>(options => options.UseSqlite(connectionString));
var app = builder.Build();
// 使用 using 确保 BloggingContext 在代码块结束后被正确释放
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<BloggingContext>();
context.Database.EnsureCreated(); // 确保数据库存在 (或者用 Migrate)
// --- C: Create (创建) ---
Console.WriteLine("Adding a new blog...");
var blog = new Blog { Url = "http://blogs.msdn.com/adonet" };
context.Blogs.Add(blog);
context.SaveChanges(); // 保存更改到数据库
Console.WriteLine($"Added blog with ID: {blog.Id}");
// --- R: Read (读取) ---
Console.WriteLine("\nReading all blogs...");
var blogs = context.Blogs.ToList();
foreach (var b in blogs)
{
Console.WriteLine($"- ID: {b.Id}, URL: {b.Url}");
}
// --- U: Update (更新) ---
Console.WriteLine("\nUpdating the blog...");
var blogToUpdate = context.Blogs.FirstOrDefault(b => b.Id == blog.Id);
if (blogToUpdate != null)
{
blogToUpdate.Url = "https://devblogs.microsoft.com/dotnet/";
context.SaveChanges();
Console.WriteLine($"Updated blog with ID: {blogToUpdate.Id} to new URL.");
}
// --- D: Delete (删除) ---
Console.WriteLine("\nDeleting the blog...");
var blogToDelete = context.Blogs.FirstOrDefault(b => b.Id == blog.Id);
if (blogToDelete != null)
{
context.Blogs.Remove(blogToDelete);
context.SaveChanges();
Console.WriteLine($"Deleted blog with ID: {blogToDelete.Id}.");
}
}
Console.WriteLine("\nAll done!");
app.Run();
代码解释:
- 创建:
new Blog()创建一个新对象实例。context.Blogs.Add(blog)将该对象添加到DbContext的“添加”状态中。context.SaveChanges()遍历所有被跟踪的更改,并生成相应的INSERTSQL 语句执行。
- 读取:
context.Blogs返回一个IQueryable<Blog>,它代表一个可以针对数据库执行的查询。.ToList()执行查询,并将结果加载到内存中的List<Blog>。- 你可以使用 LINQ 进行更复杂的查询,
context.Blogs.Where(b => b.Url.Contains("dotnet")).ToList()。
- 更新:
context.Blogs.FirstOrDefault(...)从数据库中查询一个实体,EF Core 会自动跟踪这个实体的状态。- 直接修改实体属性:
blogToUpdate.Url = "..."。 context.SaveChanges()会检测到这个实体的变化,并生成UPDATESQL 语句。
- 删除:
- 先获取要删除的实体。
context.Blogs.Remove(blogToDelete)将该实体标记为“删除”状态。context.SaveChanges()生成DELETESQL 语句。
6. 进阶主题
数据迁移 - 管理数据库架构
当你的模型发生变化时(给 Blog 类添加一个 Rating 属性),你需要更新数据库架构。
-
修改模型
// Models/Blog.cs public class Blog { public int Id { get; set; } public string Url { get; set; } public int Rating { get; set; } // 新增属性 } -
创建新的迁移
dotnet ef migrations AddRatingToBlog
这会创建一个新的迁移文件,描述了添加
Rating列的操作。 -
应用迁移
dotnet ef database update
数据库现在会包含新的
Rating列。
关系配置 - 一对一、一对多、多对多
关系通过在实体类中包含其他实体的引用来定义。
一对多:
一个 Blog 可以有多篇 Post。
// Models/Blog.cs
public class Blog
{
public int Id { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; } // 导航属性
}
// Models/Post.cs
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public Blog Blog { get; set; } // 导航属性,指向所属的 Blog
}
EF Core 会自动检测到这种关系,并创建外键约束。
多对多:
一个 Post 可以有多个 Tag,一个 Tag 也可以属于多个 Post。
// Models/Post.cs
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public List<PostTag> PostTags { get; set; } // 隐式关系
}
// Models/Tag.cs
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public List<PostTag> PostTags { get; set; } // 隐式关系
}
// EF Core 会自动创建一个连接表 "PostTags"
数据注解 vs. Fluent API
你可以使用两种方式来配置模型:
-
数据注解: 在模型类的属性上使用特性(如
[Key],[Required],[MaxLength(100)])。public class Blog { [Key] public int Id { get; set; } [Required] [MaxLength(100)] public string Url { get; set; } } -
Fluent API: 在
DbContext的OnModelCreating方法中进行配置,这是更强大、更推荐的方式,因为它可以把配置和模型分离。// Data/BloggingContext.cs protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>(entity => { entity.HasKey(e => e.Id); entity.Property(e => e.Url).IsRequired().HasMaxLength(100); }); }Fluent API 可以实现数据注解无法完成的复杂配置,例如多对多关系的自定义连接表名。
7. 最佳实践与建议
- 不要在
DbContext中暴露DbSet:考虑创建仓储模式或使用IQueryable的扩展方法来封装数据访问逻辑,避免直接暴露DbSet给业务逻辑层。 - 避免在
foreach循环中调用SaveChanges():最佳实践是一次性收集所有更改,然后调用一次SaveChanges(),这样可以减少数据库往返次数,提高性能。 - 理解变更追踪:EF Core 默认会跟踪从
DbContext中查询出的实体,对于只读场景,可以使用AsNoTracking()来禁用变更追踪,以获得性能提升。var blogs = context.Blogs.AsNoTracking().ToList();
- 处理异步操作:对于所有 I/O 操作(如数据库查询),都应使用
async和await,以避免阻塞线程,提高应用程序的响应能力。public async Task<List<Blog>> GetAllBlogsAsync() { return await context.Blogs.ToListAsync(); } - 依赖注入:始终使用依赖注入来管理
DbContext的生命周期,对于 Web 应用,通常使用AddDbContext,它会将DbContext注册为“作用域”(Scoped),即每个 HTTP 请求一个实例。
8. 学习资源
- 官方文档 (最重要!): Entity Framework Core - Microsoft Docs
这是学习 EF Core 最权威、最全面的资源。
- GitHub 仓库: dotnet/EntityFrameworkCore
可以查看源代码,了解实现细节,并提交问题。
- 视频教程:
- Channel 9: Entity Framework Core Fundamentals
- YouTube: 搜索 "Entity Framework Core Tutorial",有大量优秀的免费教程。
- 书籍:
- Entity Framework Core in Action by Jon P. Smith
- Programming Entity Framework: DbContext by Julia Lerman
这份教程为你提供了 EF Core 的坚实起点,随着实践的深入,你会逐渐掌握更多高级特性和技巧,祝你学习愉快!
