https://docs.microsoft.com/zh-cn/ef/core/dbcontext-configuration/
https://www.entityframeworktutorial.net/basics/how-entity-framework-works.aspx

1.快速开始

假如有以下的数据模型:
Entity Framework Core 快速开始_entity

分别创建实体如下:

public class Student
{
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
}

默认情况下,EF Core 将名字为ID或者ClassnameID的属性视为主键,所以这里的ID或者StudentID都可以为主键。Enrollments是导航属性,导航属性中包含与此实体相关的其它实体。Enrollments属性定义为ICollection<Enrollment>,也可以使用List<Enrollment>HashSet<Enrollment>等集合类型。使用ICollection<Enrollment>时,EF Core会默认创建HashSet<Enrollment>集合。

    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }

可以通过DatabaseGenerated特性指定主键,无需靠数据库生成。

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public int? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
  1. StudentID是外键,对应的导航属性是Student。如果一个属性的名称符合<导航属性名称><导航属性对应实体的主键属性名>,则EF会自动将其视为外键。
  2. 另外如果属性名为<导航属性对应实体的主键属性名>也可以是外键。例如上面代码的CourseID,因为Course实体的主键就是叫CourseID

这里以SqlServer数据库为例,从nuget里添加Microsoft.EntityFrameworkCore.SqlServer provider,其他数据库的provider下文有给出。

appsettings.json里添加连接字符串ConnectionStrings

{
  ....
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

创建数据库上下文SchoolContext

public class SchoolContext : DbContext
{
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
            //确保数据库被创建,如果有了这个数据库此方法不会执行任何操作即使数据库结构不正确。如果没有数据库,则按当前实体架构创建数据库。
            this.Database.EnsureCreated();
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
}

将数据库上下文服务注入到容器:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<SchoolContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
    //添加数据库异常筛选器,需要nuget包 Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
    services.AddDatabaseDeveloperPageExceptionFilter();
}

获取连接字符串时也可以写成services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection"));

2.EF是怎么工作的

Entity Framework Core 快速开始_orm_02

EF的主要工作任务有:

  • 映射实体类的结构到对应的数据库结构上
  • LINQ查询翻译为sql并执行
  • 跟踪实体类数据的改变,当调用SaveChanges方法时把这些改变翻译成sql保存到数据库。

2.1 实体数据模型(EDM)是什么

Entity Framework Core 快速开始_orm_03

EF在开始工作前的第一项任务就是创建实体数据模型(Entity Data Model,EDM)以后简称模型。EDM是一种存在于内存中的数据结构,是实体类于数据库表映射的桥梁,包含以下三部分:

  • Conceptual Model (概念模型):ef会为实体类、数据库上下文类、默认约定、配置等创建概念模型
  • Storage Model (存储模型):可以理解为数据库结构。当使用code-first模式时,从概念模型推断。当使用database-first时,从目标数据库推断。
  • Mappings (映射):负责维护概念模型与存储模型之间的转换关系

2.2 查询和保存的处理过程

查询:EDM将linq翻译成sql,然后将数据库返回的数据翻译成实体对象
Entity Framework Core 快速开始_ef_04

保存:EDM将改变的数据的翻译成sql
Entity Framework Core 快速开始_entity_05

2.3 EF怎么知道SaveChanges时哪些内容要提交到数据库?

通过Change tracking实现的。当数据从数据库读取出来之后,EF会创建数据的快照,调用保存时会与快照进行对比。

3.EF Core的配置和初始化

除了DI容器可以初始化context外也还可以手动new创建。

3.1 使用new来初始化

通过重写OnConfiguring方法将配置项传递到context里,可以使用new的方式来手动初始化context。

public class ApplicationDbContext : DbContext
{
    private readonly string _connectionString;
    public ApplicationDbContext(string connectionString)
    {
        _connectionString = connectionString;
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(_connectionString);
    }
}

如果不重写OnConfiguring方法也可以new对象。使用DbContextOptionsBuilder创建DbContextOptions对象,然后将这个对象传递到上述章节的SchoolContext里。这样通过依赖关系注入配置的context也可以显式构造,如

var contextOptions = new DbContextOptionsBuilder<SchoolContext>()
    .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test")
    .Options;

using var context = new SchoolContext(contextOptions);

3.2 配置EF Core

所有context配置的起点都是DbContextOptionsBuilder,可以通过以下三种方式获取这个builder:

  • AddDbContext方法中入参,上一小节已展示
  • override的OnConfiguring方法,上一小节已展示
  • 使用new显式构造,上一小节已展示

此外无论通过何种方式构造dbcontext,其OnConfiguring方法都会被执行。

3.3 配置数据库提供程序

每个dbcontext只能使用一个数据库提供程序。使用特定的Use***方法配置数据库提供程序,这些Use***方法一般都在对应的nuget包里。如果要用sqlserver:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test");
    }
}

常见的数据库提供程序有以下几种:

数据库 配置示例 NuGet包
SQL Server 或 Azure SQL .UseSqlServer(connectionString) Microsoft.EntityFrameworkCore.SqlServer
Azure Cosmos DB .UseCosmos(connectionString, databaseName) Microsoft.EntityFrameworkCore.Cosmos
SQLite .UseSqlite(connectionString) Microsoft.EntityFrameworkCore.Sqlite
EF Core 内存中数据库 .UseInMemoryDatabase(databaseName) Microsoft.EntityFrameworkCore.InMemory
PostgreSQL .UseNpgsql(connectionString) Npgsql.EntityFrameworkCore.PostgreSQL
MySQL/MariaDB .UseMySql((connectionString) Pomelo.EntityFrameworkCore.MySql
Oracle .UseOracle(connectionString) Oracle.EntityFrameworkCore

DbContextOptionsBuilder其它常见配置项:

DbContextOptionsBuilder方法 作用 了解更多
UseQueryTrackingBehavior 设置查询的默认跟踪行为 查询跟踪行为
LogTo 获取 EF Core 日志的一种简单方法(EF Core 5.0 及更高版本) 日志记录、事件和诊断
UseLoggerFactory 注册 Microsoft.Extensions.Logging 工厂 日志记录、事件和诊断
EnableSensitiveDataLogging 在异常和日志记录中包括应用程序数据 日志记录、事件和诊断
EnableDetailedErrors 更详细的查询错误(以性能为代价) 日志记录、事件和诊断
ConfigureWarnings 忽略或引发警告和其他事件 日志记录、事件和诊断
AddInterceptors 注册 EF Core 侦听器 日志记录、事件和诊断
UseLazyLoadingProxies 使用动态代理进行延迟加载 延迟加载
UseChangeTrackingProxies 使用动态代理进行更改跟踪 即将推出…