https://docs.microsoft.com/zh-cn/ef/core/dbcontext-configuration/
https://www.entityframeworktutorial.net/basics/how-entity-framework-works.aspx
文章目录
假如有以下的数据模型:
分别创建实体如下:
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; }
}
StudentID
是外键,对应的导航属性是Student
。如果一个属性的名称符合<导航属性名称><导航属性对应实体的主键属性名>
,则EF会自动将其视为外键。- 另外如果属性名为
<导航属性对应实体的主键属性名>
也可以是外键。例如上面代码的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"));
EF的主要工作任务有:
- 映射实体类的结构到对应的数据库结构上
- 将
LINQ
查询翻译为sql并执行 - 跟踪实体类数据的改变,当调用
SaveChanges
方法时把这些改变翻译成sql保存到数据库。
2.1 实体数据模型(EDM)是什么
EF在开始工作前的第一项任务就是创建实体数据模型(Entity Data Model,EDM)以后简称模型。EDM是一种存在于内存中的数据结构,是实体类于数据库表映射的桥梁,包含以下三部分:
- Conceptual Model (概念模型):ef会为实体类、数据库上下文类、默认约定、配置等创建概念模型
- Storage Model (存储模型):可以理解为数据库结构。当使用code-first模式时,从概念模型推断。当使用database-first时,从目标数据库推断。
- Mappings (映射):负责维护概念模型与存储模型之间的转换关系
2.2 查询和保存的处理过程
查询:EDM将linq翻译成sql,然后将数据库返回的数据翻译成实体对象
保存:EDM将改变的数据的翻译成sql
2.3 EF怎么知道SaveChanges
时哪些内容要提交到数据库?
通过Change tracking
实现的。当数据从数据库读取出来之后,EF会创建数据的快照,调用保存时会与快照进行对比。
除了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 | 使用动态代理进行更改跟踪 | 即将推出… |