Net6 EFCore CodeFirst FluentApi关系配置
十年河东十年河西,莫欺少年穷
学无止境,精益求精
微软官网:https://learn.microsoft.com/zh-cn/ef/ef6/modeling/code-first/fluent/relationships
NetCore3.1 请参考;https://www.cnblogs.com/zzy-tongzhi-cnblog/p/14366894.html
codefirst 中如果要建立两张表的主外键关系,该如何操作呢?
在EfCore 中实体关系的配置主要有3中模式
一对一 :HasOne(....).WithOne(....)
一对多 :HasOne(....).WithMany(....)
多对多 :HasMany(....).WithMany(....)
1、一对多关系【双向导航属性】
项目中新建两张表,一张为新闻表,一张为评论表,一篇新闻对应多个评论
//文章 public class Article { public long id { get; set; } public string title { get; set; } public string info { get; set; } public DateTime pubdate { get; set; } public List<Comment> comments { get; set; } = new List<Comment>(); } //评论 public class Comment { public long id { get; set; } public string uname { get; set; } public string message { get; set; } public Article articleId { get; set; } }
1.1、配置一对多关系
(方式1)

public class ArticleConfig : IEntityTypeConfiguration<Article> { public void Configure(EntityTypeBuilder<Article> builder) { builder.ToTable("T_Articles"); builder.HasMany<Comment>(A => A.comments).WithOne(A => A.articleId); } } public class CommentConfig : IEntityTypeConfiguration<Comment> { public void Configure(EntityTypeBuilder<Comment> builder) { builder.ToTable("T_Comments"); builder.Property("uname").HasMaxLength(50).IsRequired(true).HasComment("评论者姓名"); } } public class wechatDbContext : DbContext { public DbSet<Article> Articles { get; set; } public DbSet<Comment> Comments { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlServer("Data Source=LAPTOP-84R6S0FB;Initial Catalog=demo1;Integrated Security=True"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //从当前程序集命名空间加载所有的IEntityTypeConfiguration modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); } }
builder.HasMany<Comment>(A => A.comments).WithOne(A => A.articleId);
(方式2)
builder.HasOne<Article>(A => A.articleId).WithMany(A => A.comments);
1.2、显式指定外键列
//文章 一对多 public class Article { public long id { get; set; } public string title { get; set; } public string info { get; set; } public DateTime pubdate { get; set; } public List<Comment> Comments { get; set; } = new List<Comment>(); } //评论 一对多 public class Comment { public long id { get; set; } public string uname { get; set; } public string message { get; set; } public long articleId { get; set; } public Article article { get; set; } }
配置关系并指定外键列
public class ArticleConfig : IEntityTypeConfiguration<Article> { public void Configure(EntityTypeBuilder<Article> builder) { builder.ToTable("T_Articles"); //显式指定外键列 builder.HasMany<Comment>(A => A.Comments).WithOne(A => A.article).HasForeignKey(A => A.articleId); } }
建议使用这种显式指定外键列的方式进行
2、配置一对一关系
假设一篇文章只允许一条评论
实体模型对应关系如下【一对一关系需要显示指定外键】:
//文章 一对一 public class Article { public long id { get; set; } public string title { get; set; } public string info { get; set; } public DateTime pubdate { get; set; } public Comment comment { get; set; } } //评论 一对一 public class Comment { public long id { get; set; } public string uname { get; set; } public string message { get; set; } public long articleId { get; set; } public Article article { get; set; } public long acticleId { get; set; } }
(方式一)

public class ArticleConfig : IEntityTypeConfiguration<Article> { public void Configure(EntityTypeBuilder<Article> builder) { builder.ToTable("T_Articles"); } } public class CommentConfig : IEntityTypeConfiguration<Comment> { public void Configure(EntityTypeBuilder<Comment> builder) { builder.ToTable("T_Comments"); builder.Property("uname").HasMaxLength(50).IsRequired(true).HasComment("评论者姓名"); builder.HasOne<Article>(A => A.article).WithOne(d => d.comment).HasForeignKey<Comment>(A => A.acticleId); } } public class wechatDbContext : DbContext { public DbSet<Article> Articles { get; set; } public DbSet<Comment> Comments { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlServer("Data Source=LAPTOP-84R6S0FB;Initial Catalog=demo2;Integrated Security=True"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //从当前程序集命名空间加载所有的IEntityTypeConfiguration modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); } }
builder.HasOne<Article>(A => A.article).WithOne(d => d.comment).HasForeignKey<Comment>(A => A.acticleId);
(方式二)
builder.HasOne<Comment>(A => A.comment).WithOne(d => d.article).HasForeignKey<Comment>(A => A.acticleId);
3、配置多对多关系
一个老师有多个学生,一个学生有多个老师
//老师 多对多 public class Teacher { public long id { get; set; } public string tname { get; set; } public List<Student> students { get; set; } } //学生 多对多 public class Student { public long id { get; set; } public string sname { get; set; } public List<Teacher> teachers { get; set; } }
(方式一)

public class ArticleConfig : IEntityTypeConfiguration<Teacher> { public void Configure(EntityTypeBuilder<Teacher> builder) { builder.ToTable("T_Teachers"); //T_Students_Teachers 为中间表 在数据库中 多对多必须存在中间表 builder.HasMany<Student>(A => A.students).WithMany(d => d.teachers).UsingEntity(A => A.ToTable("T_Students_Teachers")); } } public class CommentConfig : IEntityTypeConfiguration<Student> { public void Configure(EntityTypeBuilder<Student> builder) { builder.ToTable("T_Studentss"); // builder.HasMany<Teacher>(A => A.teachers).WithMany(d => d.students).UsingEntity(A => A.ToTable("T_Students_Teachers")); } } public class wechatDbContext : DbContext { public DbSet<Teacher> teachers { get; set; } public DbSet<Student> students { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlServer("Data Source=LAPTOP-84R6S0FB;Initial Catalog=demo4;Integrated Security=True"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //从当前程序集命名空间加载所有的IEntityTypeConfiguration modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); } }
//T_Students_Teachers 为中间表名称 在数据库中 多对多必须存在中间表 builder.HasMany<Student>(A => A.students).WithMany(d => d.teachers).UsingEntity(A => A.ToTable("T_Students_Teachers"));
(方式二)
public class CommentConfig : IEntityTypeConfiguration<Student> { public void Configure(EntityTypeBuilder<Student> builder) { builder.ToTable("T_Studentss"); builder.HasMany<Teacher>(A => A.teachers).WithMany(d => d.students).UsingEntity(A => A.ToTable("T_Students_Teachers")); } }
4、一对多关系配置【单向导航】
使用场景,
假设我们有用户表、请假表、离职表、入职表、婚假表等,其他表都需要使用到用户表,如果此时我们使用双向导航属性,则会使用户表过于复杂,因此,此时需要单向导航属性
//文章 一对多[单向导航] public class Article { public long id { get; set; } public string title { get; set; } public string info { get; set; } public DateTime pubdate { get; set; } } //评论 一对多[单向导航] public class Comment { public long id { get; set; } public string uname { get; set; } public string message { get; set; } public long articleId { get; set; } public Article article { get; set; } }
此时,Article 无需引用评论表

public class ArticleConfig : IEntityTypeConfiguration<Article> { public void Configure(EntityTypeBuilder<Article> builder) { builder.ToTable("T_Articles"); } } public class CommentConfig : IEntityTypeConfiguration<Comment> { public void Configure(EntityTypeBuilder<Comment> builder) { builder.ToTable("T_Comments"); //显式指定外键列 builder.HasOne<Article>(A => A.article).WithMany().HasForeignKey(A => A.articleId); } } public class wechatDbContext : DbContext { public DbSet<Article> Articles { get; set; } public DbSet<Comment> Comments { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlServer("Data Source=LAPTOP-84R6S0FB;Initial Catalog=demo1;Integrated Security=True"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //从当前程序集命名空间加载所有的IEntityTypeConfiguration modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); } }
//单向导航属性、并显式指定外键列 builder.HasOne<Article>(A => A.article).WithMany().HasForeignKey(A => A.articleId);
单向导航属性用于针对公共表和其他表之前,因为公共表会被经常使用,因此尽量避免使用双向导航
5、特殊结构【树状结构】
例如公司的组织架构、权限表等
实体类如下:
//树状结构 public class OrgUint { public Guid OrgId { get; set; } public string OrgName { get; set; } public OrgUint Parent { get; set; } public List<OrgUint> Childs { get; set; } = new List<OrgUint>(); }
关系配置
public class OrgUintConfig : IEntityTypeConfiguration<OrgUint> { public void Configure(EntityTypeBuilder<OrgUint> builder) { builder.ToTable("T_OrgUints"); builder.Property(A => A.OrgId).HasMaxLength(50); builder.HasKey(A => A.OrgId); builder.HasOne<OrgUint>(A => A.Parent).WithMany(A => A.Childs);//根节点没有父节点,因此不能设置为IsRequired } }
数据测试:

static async Task Main(string[] args) { using (wechatDbContext context=new wechatDbContext()) { OrgUint org_1 = new OrgUint() { OrgName = "全球", OrgId=Guid.NewGuid() }; OrgUint org_2 = new OrgUint() { OrgName = "亚洲", OrgId = Guid.NewGuid() }; org_2.Parent = org_1; OrgUint org_3 = new OrgUint() { OrgName = "大洋洲", OrgId = Guid.NewGuid() }; org_3.Parent = org_1; OrgUint org_4 = new OrgUint() { OrgName = "欧洲", OrgId = Guid.NewGuid() }; org_4.Parent = org_1; OrgUint org_5 = new OrgUint() { OrgName = "南极洲", OrgId = Guid.NewGuid() }; org_5.Parent = org_1; OrgUint org_6 = new OrgUint() { OrgName = "中国", OrgId = Guid.NewGuid() }; org_6.Parent = org_2; OrgUint org_7 = new OrgUint() { OrgName = "日本", OrgId = Guid.NewGuid() }; org_7.Parent = org_2; OrgUint org_8 = new OrgUint() { OrgName = "英国", OrgId = Guid.NewGuid() }; org_8.Parent = org_4; // context.OrgUints.AddRange(new List<OrgUint>() { org_1, org_2, org_3, org_4, org_5, org_6, org_7, org_8 }); context.SaveChanges(); } Console.Read(); }
数据递归查询
static void Main(string[] args) { using (wechatDbContext context=new wechatDbContext()) { var org = context.OrgUints.Where(A => A.Parent == null).FirstOrDefault(); Console.WriteLine(new String('.', 1) + org.OrgName); Print(10, context, org); } Console.Read(); } static void Print(int Printlevel, wechatDbContext context,OrgUint org) { var childs = context.OrgUints.Where(A => A.Parent == org).ToList(); foreach(var item in childs) { Console.WriteLine(new String('.', Printlevel) + item.OrgName); Print(Printlevel*2, context, item); } }
以上就是CodeFitst 配置一对一 、一对多、多对多的方式
FluentApi总结
1.FluentApi简介
EF中的FluentApi作用是通过配置领域类来覆盖默认的约定。在EF中,我们通过DbModelBuilder类来使用FluentApi,它的功能比数据注释属性更强大。
使用FluentApi时,我们在context类的OnModelCreating()方法中重写配置项,一个栗子:
public class SchoolContext: DbContext { public DbSet<Student> Students { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Write Fluent API configurations here } }
我们可以把FluentApi和数据注释属性一起使用,当FluentApi和数据注释属性都配置了同一个项时,采用FluentApi中的配置。
在EF6中FluentApi可以配置领域类的以下几个方面,下表也列出了一些常用的FluentApi方法及其作用:
配置 | Fluent API 方法 | 作用 |
---|---|---|
架构相关配置 | HasDefaultSchema() | 数据库的默认架构 |
ComplexType() | 把一个类配置为复杂类型 | |
实体相关配置 | HasIndex() | 实体的的索引 |
HasKey() | 实体的主键(可其实现复合主键,[Key]在EF core中不能实现复合主键) | |
HasMany() | 1对多的或者 多对多关系 | |
HasOptional() | 一个可选的关系,这样配置会在数据库中生成一个可空的外键 | |
HasRequired() | 一个必有的关系,这样配置会在数据库中生成一个不能为空的外键 | |
Ignore() | 实体或者实体的属性不映射到数据库 | |
Map() | 设置一些优先的配置 | |
MapToStoredProcedures() | 实体的CUD操作使用存储过程 | |
ToTable() | 为实体设置表名 | |
属性相关配置 | HasColumnAnnotation() | 给属性设置注释 |
IsRequired() | 在调用SaveChanges()方法时,属性不能为空 | |
IsOptional() | 可选的,在数据库生成可空的列 | |
HasParameterName() | 配置用于该属性的存储过程的参数名 | |
HasDatabaseGeneratedOption() | 配置数据库中对应列的值怎样生成的,如计算,自增等 | |
HasColumnOrder() | 配置数据库中对应列的排列顺序 | |
HasColumnType() | 配置数据库中对应列的数据类型 | |
HasColumnName() | 配置数据库中对应列的列名 | |
IsConcurrencyToken() | 配置数据库中对应列用于乐观并发检测 |
@天才卧龙的博科人