EF实体类的配置可以使用数据注释或Fluent API两种方式配置,Fluent API配置的关键在于搞清实体类的依赖关系,按此方法配置,快速高效合理。为了方便理解,我们使用简化的实体A和B以及A、B的配置类AMap和BMap,来演示如何正确配置实体类关系的过程。

public class A
{
public int Id { get; set; }
}

public class B
{
public int Id { get; set; }
}

public class AMap : EntityTypeConfiguration<A>
{
public AMap()
{
this.HasKey(o => o.Id);
}
}

public class BMap : EntityTypeConfiguration<B>
{
public BMap()
{
this.HasKey(o => o.Id);
}
}

实体类配置

一、确定依赖关系:

假设实体B依赖于实体A(B->A),那么实体B中存在对实体A的引用。

二、实体类配置应该写在哪里?

假设B依赖于A(B->A),很显然,我们希望的是B表中生成外键(A表的主键值)。以下两种方式都可以实现相同的表结构,但毫无疑问我们应该在B的配置文件BMap中进行关系配置。

(1)B依赖于A,A可以对B的存在一无所知。

(2)A可以单独存在,配置写在哪里都不会对A表产生影响。

(3)B对A的依赖是通过在B表中生成外键(A表的主键)。

推荐的写法:

  1. public class BMap : EntityTypeConfiguration<B> { public BMap() { this.HasRequired(=> o.A).WithMany(o=>o.ListB); } }

摒弃的写法:

  1. public class AMap : EntityTypeConfiguration<A> { public AMap() { this.HasMany(=> o.ListB).HasRequired(=> o.A); } }

依赖的方向决定了使用的配置,这在实体类数量和关系复杂时尤其重要,假设有10个实体类依赖A,混合书写配置显然不可取,而在被依赖实体中配置的结果会导致经常修改A的配置文件,你甚至不肯定修改了类A的配置文件是会引起A表的变化。

三、配置依赖关系

配置文件的基类EntityTypeConfiguration包含了一系列Has方法用来配置实体类,其中HasOptional和HasRequired根据实体的引用属性配置实体关系。假设B依赖于A(B->A),HasOptional允许B单独存在,这将在B表中生成可空的外键。HasRequired不允许B单独存在,这将在B表中生成非空的外键。

public class BMap : EntityTypeConfiguration<B>
{
public BMap()
{
this.HasKey(o => o.Id);
this.HasRequired(o => o.A);
}
}

HasRequired

  1. public class BMap : EntityTypeConfiguration<B>
  2. {
  3. public BMap()
  4. {
  5. this.HasKey(o => o.Id);
  6. this.HasRequired(o => o.A);
  7. }
  8. }

public class BMap : EntityTypeConfiguration<B>
{
public BMap()
{
this.HasKey(o => o.Id);
this.HasOptional(o => o.A);
}
}

HasOptional

  1. public class BMap : EntityTypeConfiguration<B>
  2. {
  3. public BMap()
  4. {
  5. this.HasKey(o => o.Id);
  6. this.HasOptional(o => o.A);
  7. }
  8. }

四、配置关联类型

HasOptional和HasRequired分别返回OptionalNavigationPropertyConfiguration和RequiredNavigationPropertyConfiguration对象,我们使用其中的WithMany和WithOptional来配置关联的类型。

如果A:B = 1:N,我们使用WithMany。

public class BMap : EntityTypeConfiguration<B>
{
public BMap()
{
this.HasKey(o => o.Id);
this.HasOptional(o => o.A).WithMany();
}
}

1:N(外键可空)

  1. public class BMap : EntityTypeConfiguration<B>
  2. {
  3. public BMap()
  4. {
  5. this.HasKey(o => o.Id);
  6. this.HasOptional(o => o.A).WithMany();
  7. }
  8. }

public class BMap : EntityTypeConfiguration<B>
{
public BMap()
{
this.HasKey(o => o.Id);
this.HasRequired(o => o.A).WithMany();
}
}

1:N(外键不可空)

  1. public class BMap : EntityTypeConfiguration<B>
  2. {
  3. public BMap()
  4. {
  5. this.HasKey(o => o.Id);
  6. this.HasRequired(o => o.A).WithMany();
  7. }
  8. }

如果A:B= 1:1,我们使用WithOptional。1:1的关联要求外键的非空和唯一,数据库是通过表B的外键作为主键来实现。

public class BMap : EntityTypeConfiguration<B>
{
public BMap()
{
this.HasKey(o => o.Id);
this.HasRequired(o => o.A).WithOptional();
}
}

1:1

  1. public class BMap : EntityTypeConfiguration<B>
  2. {
  3. public BMap()
  4. {
  5. this.HasKey(o => o.Id);
  6. this.HasRequired(o => o.A).WithOptional();
  7. }
  8. }

四、可选导航属性

导航属性由关联类型决定,但其存在与否不会影响实体的依赖关系和关联类型。

对于B->A,如果A:B = 1:N,我们可以在A中添加ICollection<B>类型的导航属性,同时修改关系配置,将该属性传递给WithMany方法。

public class A
{
public int Id { get; set; }

public ICollection<B> BList { get; set; }
}

public class B
{
public int Id { get; set; }

public A A { get; set; }
}

public class AMap : EntityTypeConfiguration<A>
{
public AMap()
{
this.HasKey(o => o.Id);
}
}

public class BMap : EntityTypeConfiguration<B>
{
public BMap()
{
this.HasKey(o => o.Id);
this.HasOptional(o => o.A).WithMany(o => o.BList);
}
}

导航属性

  1. public class A
  2. {
  3. public int Id { get; set; }
  4.  
  5. public ICollection<B> BList { get; set; }
  6. }
  7.  
  8. public class B
  9. {
  10. public int Id { get; set; }
  11.  
  12. public A A { get; set; }
  13. }
  14.  
  15. public class AMap : EntityTypeConfiguration<A>
  16. {
  17. public AMap()
  18. {
  19. this.HasKey(o => o.Id);
  20. }
  21. }
  22.  
  23. public class BMap : EntityTypeConfiguration<B>
  24. {
  25. public BMap()
  26. {
  27. this.HasKey(o => o.Id);
  28. this.HasOptional(o => o.A).WithMany(o => o.BList);
  29. }
  30. }

如果A:B = 1:1,我们可以在A中添加B类型的导航属性,同时修改关系配置,将该属性传递给WithOptional方法。

public class A
{
public int Id { get; set; }

public B B { get; set; }
}

public class B
{
public int Id { get; set; }

public A A { get; set; }
}

public class AMap : EntityTypeConfiguration<A>
{
public AMap()
{
this.HasKey(o => o.Id);
}
}

public class BMap : EntityTypeConfiguration<B>
{
public BMap()
{
this.HasKey(o => o.Id);
this.HasRequired(o => o.A).WithOptional(o => o.B);
}
}

导航属性

  1. public class A
  2. {
  3. public int Id { get; set; }
  4.  
  5. public B B { get; set; }
  6. }
  7.  
  8. public class B
  9. {
  10. public int Id { get; set; }
  11.  
  12. public A A { get; set; }
  13. }
  14.  
  15. public class AMap : EntityTypeConfiguration<A>
  16. {
  17. public AMap()
  18. {
  19. this.HasKey(o => o.Id);
  20. }
  21. }
  22.  
  23. public class BMap : EntityTypeConfiguration<B>
  24. {
  25. public BMap()
  26. {
  27. this.HasKey(o => o.Id);
  28. this.HasRequired(o => o.A).WithOptional(o => o.B);
  29. }
  30. }

五、显式外键属性

对于B->A,如果A:B = 1:1,外键就是主键。

如果A:B = 1:N,我们可以自定义导航属性对应的外键属性,首先在B中添加显式的用于外键的属性。

public class B
{
public int Id { get; set; }

public A A { get; set; }

//public int AId { get; set; }
public int? AId { get; set; }
}

显式外键

  1. public class B
  2. {
  3. public int Id { get; set; }
  4.  
  5. public A A { get; set; }
  6.  
  7. //public int AId { get; set; }
  8. public int? AId { get; set; }
  9. }

WithMany返回DependentNavigationPropertyConfiguration对象,我们使用该对象的HasForeignKey方法,如果实体联系配置为HasOptional,则需要使用可空类型匹配。

public class BMap : EntityTypeConfiguration<B>
{
public BMap()
{
this.HasKey(o => o.Id);
this.HasOptional(o => o.A).WithMany().HasForeignKey(o => o.AId);
}
}

外键配置

  1. public class BMap : EntityTypeConfiguration<B>
  2. {
  3. public BMap()
  4. {
  5. this.HasKey(o => o.Id);
  6. this.HasOptional(o => o.A).WithMany().HasForeignKey(o => o.AId);
  7. }
  8. }

六、级联删除配置

HasForeignKey返回CascadableNavigationPropertyConfiguration对象,EF默认开启级联删除,当实体关系复杂导致无法开启级联删除时,我们使用该对象的WillCascadeOnDelete方法配置取消级联删除。

七、关于双向依赖

EF中实体的关联通过表的外键实现,1:N还是1:1都是通过外键实现。我们可以根据1:N配置的方式配置出双向依赖的表,但通常所谓的多对多都不是双向依赖。例如用户和角色、学生和课程、文章和标签等,甚至根本没有依赖,因为二者都可以独立存在,有的只是映射关系对二者的依赖,而这是1:N的问题。

我们使用EntityTypeConfiguration配置实体依赖,该类的ToTable、HasKey等实例方法都用于配置当前实体类映射的Table。HasRequired和HasOptional方法也会在对应的Table中生存外键,而HasMany方法则是其中的异类,偏偏配置的非当前实体类。

HasMany、WithMany除了在配置双向引用时替我们自动生成关系表,带来更多的是配置混乱。而所谓的自动生成关系表更是打破了我们实体类和Table的一一对应。在Microsoft.AspNet.Identity.EntityFramework 1.0中,我们可以看到IdentityUser和IdentityRole并没有通过双向引用自动生成关系表,而是定义了IdentityUserRole实体类用来映射:通过IdentityDbContext<TUser>的OnModelCreating配置我们可以看到虽然使用了HasMany配置TUser的Roles属性,但是完全可以在IdentityUserRole中配置。即使在2.0版本中依旧如此。

、常见的配置举例:

1.用户和角色:

(1)确定依赖关系:User和Role都可以单独存在,但UserRole不可以单独存在,因此存在的依赖是UserRole->User,UserRole->Role。

(2)配置依赖关系:UserRole不能单独存在,因此使用HasRequired。

(3)确定关联类型:User:UserRole==1:*;Role:UserRole=1:*,因此使用WithMany。

(4)显式的外键属性:在UserRole中添加UserId和RoleId作为显式的外键属性。

(5)可选的导航属性:在User和Role中添加ICollection<UserRole>类型的导航属性。

UserRole不应该存在重复的用户角色映射,因此使用外键作为联合主键。

public class User
{
public User()
{
this.UserRoles = new List<UserRole>();
}

public int Id { get; set; }

public string UserName { get; set; }

public ICollection<UserRole> UserRoles { get; set; }
}

public class Role
{
public Role()
{
this.UserRoles = new List<UserRole>();
}

public int Id { get; set; }

public string RoleName { get; set; }

public ICollection<UserRole> UserRoles { get; set; }
}

public class UserRole
{
public User User { get; set; }

public int UserId { get; set; }

public Role Role { get; set; }

public int RoleId { get; set; }
}

public class UserRoleMap : EntityTypeConfiguration<UserRole>
{
public UserRoleMap()
{
this.HasKey(o => new { o.UserId, o.RoleId });
this.HasRequired(o => o.User).WithMany(o => o.UserRoles).HasForeignKey(o => o.RoleId);
this.HasRequired(o => o.Role).WithMany(o => o.UserRoles).HasForeignKey(o => o.UserId);
}
}

UserRole

  1. public class User
  2. {
  3. public User()
  4. {
  5. this.UserRoles = new List<UserRole>();
  6. }
  7.  
  8. public int Id { get; set; }
  9.  
  10. public string UserName { get; set; }
  11.  
  12. public ICollection<UserRole> UserRoles { get; set; }
  13. }
  14.  
  15. public class Role
  16. {
  17. public Role()
  18. {
  19. this.UserRoles = new List<UserRole>();
  20. }
  21.  
  22. public int Id { get; set; }
  23.  
  24. public string RoleName { get; set; }
  25.  
  26. public ICollection<UserRole> UserRoles { get; set; }
  27. }
  28.  
  29. public class UserRole
  30. {
  31. public User User { get; set; }
  32.  
  33. public int UserId { get; set; }
  34.  
  35. public Role Role { get; set; }
  36.  
  37. public int RoleId { get; set; }
  38. }
  39.  
  40. public class UserRoleMap : EntityTypeConfiguration<UserRole>
  41. {
  42. public UserRoleMap()
  43. {
  44. this.HasKey(o => new { o.UserId, o.RoleId });
  45. this.HasRequired(o => o.User).WithMany(o => o.UserRoles).HasForeignKey(o => o.RoleId);
  46. this.HasRequired(o => o.Role).WithMany(o => o.UserRoles).HasForeignKey(o => o.UserId);
  47. }
  48. }

2.节点树:

(1)确定依赖关系:Category自依赖,Category->Category

(2)配置依赖关系:Category可以单独存在,因此使用HasOptional。

(3)确定关联类型:Category:Category==1:*,因此使用WithMany。

(4)显式的外键属性:在UserRole中添加ParentId,由于Category可以单独存在,ParentId为可空类型。

(5)可选的导航属性:在Category中添加ICollection<Category>类型的导航属性。

public class Category
{
public int Id { get; set; }

public string Name { get; set; }

public int? ParentId { get; set; }

public Category Parent { get; set; }

public ICollection<Category> Children { get; set; }
}

public class CategoryMap : EntityTypeConfiguration<Category>
{
public CategoryMap()
{
this.HasKey(o => o.Id);
this.HasOptional(o => o.Parent).WithMany(o => o.Children).HasForeignKey(o => o.ParentId);
}
}

Category->Category

  1. public class Category
  2. {
  3. public int Id { get; set; }
  4.  
  5. public string Name { get; set; }
  6.  
  7. public int? ParentId { get; set; }
  8.  
  9. public Category Parent { get; set; }
  10.  
  11. public ICollection<Category> Children { get; set; }
  12. }
  13.  
  14. public class CategoryMap : EntityTypeConfiguration<Category>
  15. {
  16. public CategoryMap()
  17. {
  18. this.HasKey(o => o.Id);
  19. this.HasOptional(o => o.Parent).WithMany(o => o.Children).HasForeignKey(o => o.ParentId);
  20. }
  21. }

Entity Framework 6 Code First 实践系列(1):实体类配置-根据依赖配置关系和关联的更多相关文章

  1. 【转】Entity Framework 6 Code First 实践系列(1):实体类配置-根据依赖配置关系和关联

    本文转自:http://www.cnblogs.com/easygame/p/3622893.html EF实体类的配置可以使用数据注释或Fluent API两种方式配置,Fluent API配置的关 ...

  2. Entity Framework Core Code First 项目实践

    Entity Framework Core Code First 实践 任何一种技术的出现都是为了解决一系列特定的问题,只有了解了技术所要解决的关键问题,才能理解它的真正用途,之后,才能在实践中用好它 ...

  3. 《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述

    微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF ...

  4. AppBox升级进行时 - 拥抱Entity Framework的Code First开发模式

    AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. 从Subsonic到Entity Framework Subsonic最早发布 ...

  5. 【极力分享】[C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例【转载自https://segmentfault.com/a/1190000004152660】

      [C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例 本文我们来学习一下在Entity Framework中使用Cont ...

  6. Entity Framework 6 Code First新特性:支持存储过程

    Entity Framework 6提供支持存储过程的新特性,本文具体演示Entity Framework 6 Code First的存储过程操作. Code First的插入/修改/删除存储过程 默 ...

  7. 创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表

    创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表 创建数据模型类(POCO类) 在Models文件夹下添 ...

  8. MVC2、MVC3、MVC4、MVC5之间的区别 以及Entity Framework 6 Code First using MVC 5官方介绍教程

    现在MVC的技术日趋成熟,面对着不同版本的MVC大家不免有所迷惑 -- 它们之间有什么不同呢?下面我把我搜集的信息汇总一下,以便大家能更好的认识不同版本MVC的功能,也便于自己查阅. View Eng ...

  9. Entity Framework 之 Code First

    使用NuGet助您玩转代码生成数据————Entity Framework 之 Code First [前言] 如果是Code First老鸟或者对Entity Framework不感兴趣,就不用浪费 ...

随机推荐

  1. Lenovo笔记本电脑进入BIOS的方法

    使用NOVO键开机进入BIOS的操作方法 适用范围:2012年后发布的部分笔记本产品,含:IdeaPad全系列.Lenovo G系列部分IdeaPad U或S系列,YOGA/FLEX全系列产品Leno ...

  2. NYOJ 995 硬币找零

    硬币找零 时间限制:1000 ms  |  内存限制:65535 KB 难度:3   描述 在现实生活中,我们经常遇到硬币找零的问题,例如,在发工资时,财务人员就需要计算最少的找零硬币数,以便他们能从 ...

  3. 84. Spring Boot集成MongoDB【从零开始学Spring Boot】

    至于MongoDB网上有很多相关的资料,所以在这里不进行过多的介绍,我们在这里主要是介绍下如何将mongodb与spring boot结合使用.本节大纲: (1) 准备工作: (2) 新建一个mave ...

  4. 九度oj 题目1528:最长回文子串

    题目描述: 回文串就是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串. 回文子串,顾名思义,即字符串中满足回文性质的子串. 给出一个只由小写英文字符a,b,c...x, ...

  5. 九度oj 题目1173:查找

    题目描述: 输入数组长度 n 输入数组      a[1...n] 输入查找个数m 输入查找数字b[1...m]  输出 YES or NO  查找有则YES 否则NO . 输入: 输入有多组数据. ...

  6. 【Luogu】P3116会议时间(拓扑排序,DP)

    题目链接 本题使用拓扑排序来规划DP顺序.设s[i][j]表示i步是否能走到j这个点,e[i][j]表示i步是否能走到j这个点——用第二条路径.因为要满足无后效性和正确性,只有第i个点已经全部更新完毕 ...

  7. [BZOJ2342] [Shoi2011]双倍回文(manacher)

    传送门 manacher...... 先跑一边manacher是必须的 然后枚举双倍回文串的对称轴x 把这个双倍回文串分成4段,w wR w wR 发现,只有当 y <= x + p[x] / ...

  8. POJ 1860: Currency Exchange 【SPFA】

    套汇问题,从源点做SPFA,如果有一个点入队次数大于v次(v表示点的个数)则图中存在负权回路,能够套汇,如果不存在负权回路,则判断下源点到自身的最长路是否大于自身,使用SPFA时松弛操作需要做调整 # ...

  9. [HNOI2010]CHORUS 合唱队 (区间DP)

    题目描述 对于一个包含 NN 个整数的数列 AA ,我们可以把它的所有元素加入一个双头队列 BB . 首先 A1A1 作为队列的唯一元素,然后依次加入 A2∼ANA2∼AN ,如果 Ai<Ai− ...

  10. Spoj-BLMIRINA Archery Training

    Mirana is an archer with superpower. Every arrow she shoots will get stronger the further it travels ...