.NET Entity Framework (EF) Core 全方位教程

目录

  1. 什么是 EF Core?为什么选择它?
  2. 核心概念:EF Core 是如何工作的?
  3. 环境准备:安装与配置
  4. 第一步:创建你的第一个 EF Core 项目
    • [创建数据库模型]
    • [创建 DbContext]
    • [配置数据库连接]
    • [创建并迁移数据库]
  5. CRUD 操作详解
    • [创建 - Add() 和 SaveChanges()]
    • [读取 - 查询:LINQ to Entities]
    • [更新 - 跟踪、修改和 SaveChanges()]
    • [删除 - Remove() 和 SaveChanges()]
  6. 进阶主题
    • [数据迁移 - 管理数据库架构]
    • [关系配置 - 一对一、一对多、多对多]
    • [数据注解 vs. Fluent API]
    • [处理并发冲突]
    • [性能优化:延迟加载 vs. 预加载 vs. 显式加载]
  7. 最佳实践与建议
  8. 学习资源

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. 环境准备:安装与配置

前提条件:

安装 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() 遍历所有被跟踪的更改,并生成相应的 INSERT SQL 语句执行。
  • 读取:
    • 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() 会检测到这个实体的变化,并生成 UPDATE SQL 语句。
  • 删除:
    • 先获取要删除的实体。
    • context.Blogs.Remove(blogToDelete) 将该实体标记为“删除”状态。
    • context.SaveChanges() 生成 DELETE SQL 语句。

6. 进阶主题

数据迁移 - 管理数据库架构

当你的模型发生变化时(给 Blog 类添加一个 Rating 属性),你需要更新数据库架构。

  1. 修改模型

    // Models/Blog.cs
    public class Blog
    {
        public int Id { get; set; }
        public string Url { get; set; }
        public int Rating { get; set; } // 新增属性
    }
  2. 创建新的迁移

    dotnet ef migrations AddRatingToBlog

    这会创建一个新的迁移文件,描述了添加 Rating 列的操作。

  3. 应用迁移

    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: 在 DbContextOnModelCreating 方法中进行配置,这是更强大、更推荐的方式,因为它可以把配置和模型分离。

    // 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. 最佳实践与建议

  1. 不要在 DbContext 中暴露 DbSet:考虑创建仓储模式或使用 IQueryable 的扩展方法来封装数据访问逻辑,避免直接暴露 DbSet 给业务逻辑层。
  2. 避免在 foreach 循环中调用 SaveChanges():最佳实践是一次性收集所有更改,然后调用一次 SaveChanges(),这样可以减少数据库往返次数,提高性能。
  3. 理解变更追踪:EF Core 默认会跟踪从 DbContext 中查询出的实体,对于只读场景,可以使用 AsNoTracking() 来禁用变更追踪,以获得性能提升。
    var blogs = context.Blogs.AsNoTracking().ToList();
  4. 处理异步操作:对于所有 I/O 操作(如数据库查询),都应使用 asyncawait,以避免阻塞线程,提高应用程序的响应能力。
    public async Task<List<Blog>> GetAllBlogsAsync()
    {
        return await context.Blogs.ToListAsync();
    }
  5. 依赖注入:始终使用依赖注入来管理 DbContext 的生命周期,对于 Web 应用,通常使用 AddDbContext,它会将 DbContext 注册为“作用域”(Scoped),即每个 HTTP 请求一个实例。

8. 学习资源

这份教程为你提供了 EF Core 的坚实起点,随着实践的深入,你会逐渐掌握更多高级特性和技巧,祝你学习愉快!