banner
Chenh

Chenh

Entity Framework Core

Entity Framework Core#

1. 导航属性#

// A |->* B
public class A{
  //...
  public List<B> Bs{get;set;}
}

public class B{
  // ...
  public B B{set;get;}
}

导航属性可以是任何列表类型。

2. 数据库上下文 ⚠️#

用来协调 EF 和数据库的管理类。#

  • 一个 Context 是一个继承自 DbContext 的非抽象类。这个类的功能首先是公开所有实体的集合,操作这些集合可以在存储中读取或保存数据,除此之外 Context 保存数据的模型。在使用前需要正确配置各个实体的模型以及其他必要的配置。
  • Context 的另一个重要的功能就是跟踪对实体的更改,以便我们在保存更改时,它知道该如何处理。
  • Context 跟踪的每个实体所处的以下状态之一:未更改 /added,已修改 /modified,已添加 /added,已删除 /deleted已分离 /detached。可以将上下文视为沙箱,我们可以在其中对实体集合进行更改,然后通过一次保存操作应用这些更改。
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }
				// 实体集 DbSet 对应数据库表
        // 由于Courses表包含了 下面两个表,因此,即使不写下方两表,EF也会自动包含
        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
      	// 覆盖默认行为,设定表名
      	protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

DbContext 钩子方法#

  • OnConfiguring 方法:当 context 需要配置它的 provider 和 链接字符串的时候,该方法会被调用,这为开发者提供了介入的入口。
  • OnModelCreating 方法:在 EF Core 组装数据模型时会被自动调用,你将会在这个方法里添加构建模型的代码。
  • SaveChanges 方法:当我们希望将修改保存到数据库时进行调用。 返回为受保存操作影响的记录数。

Database Provider / 数据库提供程序#

  • EF core 是数据库无关的。因此不同的数据库需要为其提供特定接口
  • 引入相关数据库提供程序包,并在 Context 的 OnConfiguring 中添加对应配置方法(UseSqlServer)

3. 服务注册#

// Startup.cs
/* Using */
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
/* Start up */
public void ConfigureServices(IServiceCollection services)
{
  services.AddDbContext<SchoolContext>(
    options=>options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
  services.AddControllersWithViews();
  // 开发环境 异常筛选功能
  services.AddDatabaseDeveloperPageExceptionFilter();
}
// appsetting.json
{
  "ConnectionStrings": {
    "DefaultConnection": "数据库字符串"
  },
}

4. 创建数据模型#

  • 创建数据模型需要 实体 Entity 类型。实体类型就是一系列 C# 类,通常与数据库对应。

  • 配置 实体类型数据库实体 之间的关系。有两种方式:”fluent API“” 数据注释 “

    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    // 配置 对应表名 和 对应数据库架构名
    [Table("blogs",Schema="dbo")]
    public class Blog{
      [Key]	// 主键,通常efcore会自动识别
      public int BlogId{set;get;}
      [MaxLength(400)] // 最大长度
      public string Url{set;get;}
      [NotMapped] // 排除映射
      public DataTime LoadedFromDatabase {set;get;}
      [Column("rating",TypeName="decimal(5,2)")] // 映射列名和类型
      public decimal Rating {set;get;}
    }
    // 使用 fluent API 配置默认架构名 
    protected override void OnModelCreating(ModelBuilder modelBuilder){
      modelBuilder.HasDefaultSchema("dbo");
    }
    

“实体引用 / 引用导航” 配置 一对一#

//一Blog 对应 一个 Customer
class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<BlogImage> BlogImages { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //设置 Blog与BlogImage 一对一关系
        modelBuilder.Entity<Blog>()
            .HasOne(b => b.BlogImage)
            .WithOne(i => i.Blog)
            .HasForeignKey<BlogImage>(b => b.BlogForeignKey);
    }
}
public class Blog
{
    public Blog()
    {
        Posts = new HashSet<Post>();
    }

    public int BlogId { get; set; }
    
    public string Name { get; set; }
    public string Url { get; set; }
    public int? ImageId { get; set; }
    // 引用导航 efcore 可以自动配置 写法见官方文档
    public BlogImage BlogImage { get; set; }
    public HashSet<Post> Posts { get; set; }
}
public class BlogImage
{
    public int BlogImageId { get; set; }
    public string ImageUrl { get; set; }
    public string Caption { get; set; }
}

“实体集合 / 集合导航” 配置 :一对多#

//实体集合属性的配置
class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //配置 Blog 的一对多关系
        modelBuilder.Entity<Blog>()
            .HasMany(b => b.Posts)
            .WithOne(p => p.Blog)
            .HasForeignKey(p => p.BlogId);
    }
}

public class Blog
{
    public Blog()
    {
      	// 因为有 HashSet 导航,构造函数要添加此内容
        Posts = new HashSet<Post>();
    }

    public int BlogId { get; set; }

    public string Name { get; set; }
    public string Url { get; set; }
    public int? ImageId { get; set; }
    public BlogImage BlogImage { get; set; }
  	// 集合导航,表示一对多关系,需要HashSet辅助
    public HashSet<Post> Posts { get; set; }
}

public class Post
{
    [Key]
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

总结#

数据模型的作用除了说明数据结构,还需要说明数据关系。关系也是数据库的核心。

EF core 支持多种方式描述模型关系,使用注释是最直观简单的方法,特殊的关系还需要使用 FluentAPI 辅助。

但两者都是在 ModelBuilder 中配置相关行为。

5. 迁移 从模型到数据库#

迁移是使用 EF core 迁移工具从实体模型创建和管理数据库。

迁移过程#

  1. 按照上述步骤,构建实体模型和关系,会有一个 的类。通常是你打算的数据库名称 + Context,这个类继承与 DbContext。EF core 正是依据这个文件中的实体模型和关系说明来生成数据库的。

  2. 打开终端

  3. 首先确定安装 .NET core CLI 工具

    # 全局安装 dotnet-ef 工具
    dotnet tool install --global dotnet-ef
    # 更新 
    dotnet tool update --global dotnet-ef
    # 为 项目添加 EFCoreDesign 包
    dotnet add package Microsoft.EntityFrameworkCore.Design # 也可以使用Nuget管理器
    # 检查 工具安装情况
    dotnet ef
    

    ⚠️ EF Core 工具参考 (.NET CLI) - EF Core | Microsoft Learn 工具的详细使用方法见此处

  4. 在工程目录下打开终端:

    # 创建一个迁移
    dotnet ef migrations add InitialCreate
    ## 该命令会生成一个 migration 文件夹,用来记录迁移,也可以直接修改其内容。
    # 创建数据库架构
    dotnet ef database update
    

    ⚠️ 关于验证问题,可以在连接字符串中添加TrustServerCertificate=true;Encrypt=true

  5. Done.

发展模型#

当你的实体模型发生改变,我们需要重新更新数据库。

首先必须要明确,迁移工具生成的 migrations/ 系列文件,是迁移记录,是数据库结构演进的完整说明。它包含了一份数据库结构快照,和一份迁移文件。

因此,当模型改变时,我们可以手动更改 migrations/ 文件,并再次执行dotnet ef database update来应用迁移。

为了保留数据库演进的记录,我们使用以下流程:

  1. 创建新的迁移

    # 这里最好使用有意义的迁移命名
    dotnet ef migrations add AddSomeTable
    
  2. 应用迁移 dotnet ef database update

说明#

阅读 migration/ 文件夹,里面有一系列带时间戳和迁移名称的.cs文件,用来指示每次迁移执行的动作。可以看出,当实体模型改变时,EFcore 会自动比较并进行增量更新,而非重新生成数据库。

另一个文件是数据库快照的.cs文件,可以看出里面是真实的数据库表实体模型。是上一次更新有的数据库快照。

两种文件都是 CSharp 文件,可以加入到版本管理中,从而记录工程开发过程中的数据库演进。

6 其他#

排除类型#

若当前 Context 引用了其他 Context 中的类型,会在迁移时出现冲突(引用的对象在当前 Context 并不存在,出现错误是正常的),此时要从 Context 中排除该类型。

// 这是引用Context类
// 在模型创建钩子中,排除不存在的类型。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<IdentityUser>()
        .ToTable("AspNetUsers", t => t.ExcludeFromMigrations());
}

列重命名#

EFCore 无法区分列的重命名或删除,因此,当你重命名列时,EFcore 会生成 Delete-Create 的操作。这可能会导致数据丢失。需要手动更改。

// EFcore 生成的迁移动作
migrationBuilder.DropColumn(
    name: "Name",
    table: "Customers");

migrationBuilder.AddColumn<string>(
    name: "FullName",
    table: "Customers",
    nullable: true);

// 需要将其修改为
migrationBuilder.RenameColumn(
    name: "Name",
    table: "Customers",
    newName: "FullName");

在迁移中引入 SQL 行为#

假设要将实体中原来的 FirstnameLastName 合并成 FullName 重新存储,并删除前两个列。

EFcore 不会自动识别这个行为,需要手动引入 SQL 语句用来迁移。

migrationBuilder.AddColumn<string>(
    name: "FullName",
    table: "Customer",
    nullable: true);

migrationBuilder.Sql(
@"
    UPDATE Customer
    SET FullName = FirstName + ' ' + LastName;
");

migrationBuilder.DropColumn(
    name: "FirstName",
    table: "Customer");

migrationBuilder.DropColumn(
    name: "LastName",
    table: "Customer");

⚠️ 此流程是有顺序的。

当要利用旧数据进行迁移时,可以使用 SQL

自定义 SQL 迁移#

可以在不改变模型的情况下,生成一个空迁移,在手动填充 SQL 语句。

SQL 可以操作模型不知道的数据库对象。

migrationBuilder.Sql(
@"
    EXEC ('CREATE PROCEDURE getFullName
        @LastName nvarchar(50),
        @FirstName nvarchar(50)
    AS
        RETURN @LastName + @FirstName;')");

⚠️ 当某个语句必须是 SQL 批处理中的第一个或唯一一个语句时,请使用 EXEC。 它还可以用来解决幂等迁移脚本中的分析程序错误,当表中当前不存在引用的列时,可能会发生此类错误。

⚠️ 通常 EFCore 使用事务执行迁移操作。当某些数据库不支持该操作时,可以向migrationBuilder.Sql中传入参数suppressTransaction: true 来选择不使用事务。

7. 迁移管理#

删除迁移#

使用 dotnet ef migrations remove 来移除 上一个迁移

⚠️ 请勿移除已应用的迁移!

列出迁移#

dotnet ef migrations list 列出所有迁移

重置所有#

直接删除 Migration 文件夹即可删除所有迁移记录,详情:

管理迁移 - EF Core | Microsoft Learn

应用迁移#

  1. 生成 SQL 脚本,从零开始生成数据库。这是将工程模型部署到生产环境的最佳实践。

    dotnet ef migrations script [from] [to]
    

    From to 分别是迁移版本名称。to 默认是最新。可以使用上述代码回退。

  2. 自动比较 SQL 脚本。对于将迁移应用在现有数据库,但并不清楚其当前版本的情况下。efcore 会检索迁移历史记录,自动更新。

    dotnet ef migrations script --idempotent
    
  3. 使用命令行工具升级数据库,这是开发环境测试环境的最佳实践

    dotnet ef database update [to]
    
  4. 程序运行时执行迁移,这是本地开发测试是的最佳实践,可以省去每次启动都手动迁移的步骤。

    // 在 .net 运行主函数中添加
    var host = CreateHostBuilder(args).Build();
    using(var scope = host.Services.CreateScope()){
      var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
      younameContext.Database.Migrate(); // 这句是关键。
    }
    host.Run();
    

    运行时应用迁移 - Microsoft Learn

  5. 创建捆绑包

    捆绑 - Microsoft Learn

团队管理#

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。