Entity Framework Core再一次让开发者领略到框架的强大力量,它不再是一个程序库,而是先进数据库设计手段的典范。
本文简要介绍EFCore的基本用法,更多干货可参考官方文档。
简介
- 官方文档:Entity Framework Core 概述 - EF Core | Microsoft Learn
- 是什么?
- 微软的ORM(Object Relational Mapper,对象关系映射)
- 原理:在数据库原生访问接口上封装一层,应用开发者将关系数据作为业务模型来进行使用,而不用关心数据库访问的具体操作
- 优点?
- 开发效率大大提高
- 开源且持续更新
- 访问多种数据库
- 将程序和数据库进行隔离
- 灵活,支持三种开发模式
- 缺点?
- 抽象封装越多,开发效率越高,但性能可能不及原生SQL语句。
- EF系统架构
- 增强版ADO.NET技术
- C#创建LINQ查询时,EF框架引擎会连接一个provider,将LINQ转换成SQL语句,发往数据库
- 数据库持久化
- EDM:Entity Data Model,实体数据模型
-
ORM
- ORM是允许开发者使用面向对象的编程语言访问RDBMS数据的一系列技术
- 采用原生SQL语句访问数据库,存在很多问题(效率、维护成本等)
-
EF历史
- 微软第一款ORM工具:LINQ to SQL
- 第一版:Database First
- 第二版(EF4):开始支持Model-First,可根据设计面板创建实体类,生成数据库
- EF4.1:引入Code First
-
EF的三种开发风格
- Database First
- 如果数据库已经存在,花一点时间就可以编写数据访问层,从数据库生成EDM,稍作修改即可
- 对遗留的数据库进行开发
- 数据为中心的应用开发时,数据库本身会频繁修改
- Code First
- 高度以领域为中心并且领域模型类创建优先的应用程序
- 数据库更多是一种领域模型的持久化机制,即数据库中没有逻辑
- 数据库不会手动修改,基于模型类而修
- Model First
- 使用EDM可视化设计器设计模型,数据库和类将通过该模型生成
Code First
数据迁移
MySQL
1 2 3 4 5
| { "ConnectionStrings": { "MySQLConnection": "server=localhost;port=3306;uid=root;pwd=lizi710;database=CodeFirst" }, }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema;
namespace CodeFirst.Models { [Table("类1")] public class Class1 { [Key] public Guid Class1Id { get; set; } public string Name { get; set; } }
[Table("类2")] public class Class2 { public int Id { get; set; } public string Name { get; set; } }
[Table("基类")] public class BaseClass { [Key] public Guid Id { get; set; } public string BaseClassName { get; set; } }
public enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }
[Table("product")] public class Product : BaseClass { [Key] [Column("id")] public int Id { get; set; }
[Column("name")] [MaxLength(50)] public string Name { get; set; }
[Column("Category")] [NotMapped] public string Category { get; set; }
[Column("price")] public decimal Price { get; set; }
public Weekday Day { get; set; } public Class1 C1 { get; set; } public List<Class2> C2s { get; set; } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| using Microsoft.EntityFrameworkCore;
namespace CodeFirst.Models { public partial class ProductContext : DbContext { public ProductContext() { }
public ProductContext(DbContextOptions<ProductContext> options) : base(options) { }
public virtual DbSet<Product> Product { get; set; } = null!; } }
|
1 2 3 4 5
| builder.Services.AddDbContext<ProductContext>(opt => { string connectionString = builder.Configuration.GetConnectionString("MySQLConnection"); var serverVersion = ServerVersion.AutoDetect(connectionString); opt.UseMySql(connectionString, serverVersion); });
|
1 2
| add-migration v1 update-database
|
Oracle
1 2 3 4 5
| { "ConnectionStrings": { "OracleConnection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCLPDB)));Persist Security Info=True;User ID=lihao;Password=lizi710;" }, }
|
1 2 3
| builder.Services.AddDbContext<MyDbContext>(opt => { string connectionString = builder.Configuration.GetConnectionString("OracleConnection"); opt.UseOracle(connectionString);
|
SQLite
1 2 3 4 5
| { "ConnectionStrings": { "MySQLConn": "FileName=./demo.db" }, }
|
1 2 3 4 5
| builder.Services.AddDbContext<MyDbContext>(opt => { string? connStr = builder.Configuration.GetConnectionString("MySQLConn"); if (connStr == null) return; opt.UseSqlite(connStr); });
|
DB First
- 方式1:EF Core Power Tools
- 方式2:通过nuget包管理其实现
- 安装nuget包,同Code First所需NuGet包
- 通过nuget包生成(通过OutputDir指定模型生成的文件夹)
1
| Scaffold-DbContext "server=localhost;port=3306;user=root;password=lizi710;database=mysqlbzbh" -Provider "Pomelo.EntityFrameworkCore.MySql" -OutputDir Models
|
常规实体关系
一对一
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Order { public long Id { get; set; } public string Name { get; set; } public string Address { get; set; } public Delivery Delivery { get; set; } public long DeliveryID { get; set; } } public class Delivery { public long Id { get; set; } public string CompanyName { get; set; } public string Number { get; set; } public Order? Order { get; set; } }
|
- 如果只在其中一个实体类中定义导航属性,那么将在该表中生成外键(这种方式更简洁)
1 2 3 4 5 6 7 8 9 10 11
| public class Order { public long Id { get; set; } public string Name { get; set; } public string Address { get; set; } public Delivery Delivery { get; set; } } public class Delivery { public long Id { get; set; } public string CompanyName { get; set; } public string Number { get; set; } }
|
一对多
1 2 3 4 5 6 7 8 9 10 11
| public class Article { public long Id { get; set; } public string Title { get; set; } public string Content { get; set; } public List<Comment> Comments { get; set; } = new List<Comment>(); } public class Comment { public long Id { get; set; } public Article Article { get; set; } public string Message { get; set; } }
|
- 可以只在“一方”实体类中定义导航属性,生成表时会自动在“多方”表中设置外键
1 2 3 4 5 6 7 8 9 10
| public class Article { public long Id { get; set; } public string Title { get; set; } public string Content { get; set; } public List<Comment> Comments { get; set; } = new List<Comment>(); } public class Comment { public long Id { get; set; } public string Message { get; set; } }
|
当然,也可以只在“多方”实体类中定义导航属性,生成表时会自动在“多方”表中设置外键。这时,表结构会与一对一关系一样,但在一对一关系表中外键具有唯一性,定义时应该显示指定。
- 与一对一关系一样,一对多关系可以由导航属性及外键确定,不用手动配置
多对多
- 多对多实体关系比较简单,需要在两个实体类中都设置导航属性,缺少其中一个就会被当做一对多关系了
1 2 3 4 5 6 7 8 9 10 11
| public class Student { public long Id { get; set; } public string Name { get; set; } public List<Teacher> Teachers { get; set; } }
public class Teacher { public long Id { get; set; } public string Name { get; set; } public List<Student> Students { get; set; } }
|
这时,会生成一张新表,表名为StudentTeacher,其中包含两个外键:StudentsId和TeachersId
1 2 3 4
| modelBuilder.Entity<Student>() .HasMany(s => s.Teachers) .WithMany(t => t.Students) .UsingEntity("StudentTeacher");
|
导航属性的查询
可以使用Include查询导航属性,但是这种方法基于连接操作,可能导致“笛卡尔爆炸”。这是建议使用拆分查询方式,性能更好。
1 2 3 4 5 6 7
| using (var context = new BloggingContext()) { var blogs = context.Blogs .Include(blog => blog.Posts) .AsSplitQuery() .ToList(); }
|
复杂实体关系
继承关系
对于以下实体关系:
1 2 3 4 5 6 7
| public class Person { public int Id { get; set; } public string Name { get; set; } } public class Student : Person { public string Grade { get; set; } }
|
- 方法1:
public DbSet<Person> Person { get; set; }
- 方法2:
public DbSet<Student> Student { get; set; }
这种方式只映射子类,字段包括子类字段和其从父类继承来的字段,简单来说,子类只利用父类的字段。
1 2 3 4 5 6 7 8 9 10 11 12
| public class MyDbContext : DbContext { public DbSet<Person> Person { get; set; } public DbSet<Student> Student { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Person>() .HasDiscriminator<string>("PersonType") .HasValue<Person>("Person") .HasValue<Student>("Student"); base.OnModelCreating(modelBuilder); } }
|
这种方式以子类和父类的字段来映射成表,自动添加一个表示实例类型的字段Discriminator;可以同时存储子类和父类的实例。
枚举类型
实体类的枚举类型可以在写数据库时转成int类型,可以在读数据库时还原为枚举类型。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Student { public long Id { get; set; } public string Name { get; set; } public EnumStudentType StudentType { get; set; } } public enum EnumStudentType { Grade1, Grade2, Grade3, Grade4, Grade5, Grade6 }
|
下面定义转换规则:
1 2 3 4 5 6 7 8
| protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); modelBuilder.Entity<Student>() .Property(e => e.StudentType) .HasConversion(p => (int)p, p => (EnumStudentType)p); }
|
数据库配置
Fluent API
集中配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| internal class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .IsRequired(); } }
public class Blog { public int BlogId { get; set; } public string Url { get; set; } }
|
分组配置
对每张表进行独立配置,需要继承自IEntityTypeConfiguration
泛型类:
1 2 3 4 5
| public class BlogEntityTypeConfiguration : IEntityTypeConfiguration<Blog> { public void Configure(EntityTypeBuilder<Blog> builder) { builder.Property(b => b.Url).IsRequired(); } }
|
这时,需要在DbContext
类的OnModelCreating()
方法中指定配置:
1
| new BlogEntityTypeConfiguration().Configure(modelBuilder.Entity<Blog>());
|
或者批量指定:
1
| modelBuilder.ApplyConfigurationsFromAssembly(typeof(BlogEntityTypeConfiguration).Assembly);
|
也可以通过EntityTypeConfigurationAttribute
注解来指定:
1 2 3 4 5
| [EntityTypeConfiguration(typeof(BlogEntityTypeConfiguration))] public class Blog { public int BlogId { get; set; } public string Url { get; set; } }
|
数据注解
有许多数据注解用以配置数据表,以下是简单示例:
1 2 3 4 5 6 7 8
| [Table("Blogs")] [Comment("博客表")] public class Blog { [Comment("主键")] public int BlogId { get; set; } [Required] public string Url { get; set; } }
|
内置约定
EF Core 包括许多默认启用的模型生成约定,明白这些默认规则能帮助我们简化数据库设计。
数据库文档
- SmartSQL:可生成各种格式的数据库文档
- 在实体类中使用Comment对类名和属性名进行标注,相当于给表和字段添加说明