EF实体类的配置可以使用数据注释或Fluent API两种方式配置,Fluent API配置的关键在于搞清实体类的依赖关系,按此方法配置,快速高效合理。为了方便理解,我们使用简化的实体A和B以及A、B的配置类AMap和BMap,来演示如何正确配置实体类关系的过程。
publicclassA { publicintId{get;set;} } publicclassB { publicintId{get;set;} } publicclassAMap:EntityTypeConfiguration<A> { publicAMap() { this.HasKey(o=>o.Id); } } publicclassBMap:EntityTypeConfiguration<B> { publicBMap() { this.HasKey(o=>o.Id); } } 实体类配置
一、确定依赖关系:
假设实体B依赖于实体A(B->A),那么实体B中存在对实体A的引用。
二、实体类配置应该写在哪里?
假设B依赖于A(B->A),很显然,我们希望的是B表中生成外键(A表的主键值)。以下两种方式都可以实现相同的表结构,但毫无疑问我们应该在B的@R_403_382@BMap中进行关系配置。
(1)B依赖于A,A可以对B的存在一无所知。
(2)A可以单独存在,配置写在哪里都不会对A表产生影响。
(3)B对A的依赖是通过在B表中生成外键(A表的主键)。
推荐的写法:
publicclassBMap:EntityTypeConfiguration<B>{publicBMap(){this.Hasrequired(o=>o.A).WithMany(o=>o.ListB);}} 摒弃的写法: publicclassAMap:EntityTypeConfiguration<A>{publicAMap(){this.HasMany(o=>o.ListB).Hasrequired(o=>o.A);}}
依赖的方向决定了使用的配置,这在实体类数量和关系复杂时尤其重要,假设有10个实体类依赖A,混合书写配置显然不可取,而在被依赖实体中配置的结果会导致经常修改A的@R_403_382@,你甚至不肯定修改了类A的@R_403_382@是会引起A表的变化。
三、配置依赖关系
@R_403_382@的基类EntityTypeConfiguration包含 了一系列Has方法用来配置实体类,其中HasOptional和Hasrequired根据实体的引用属性配置实体关系。假设B依赖于 A(B->A),HasOptional允许B单独存在,这将在B表中生成可空的外键。Hasrequired不允许B单独存在,这将在B表中生成 非空的外键。
publicclassBMap:EntityTypeConfiguration<B> { publicBMap() { this.HasKey(o=>o.Id); this.Hasrequired(o=>o.A); } } Hasrequired
publicclassBMap:EntityTypeConfiguration<B> { publicBMap() { this.HasKey(o=>o.Id); this.HasOptional(o=>o.A); } } HasOptional
四、配置关联类型
HasOptional和Hasrequired分别返回OptionalNavigationPropertyConfiguration和requiredNavigationPropertyConfiguration对象,我们使用其中的WithMany和WithOptional来配置关联的类型。
如果A:B =1:N,我们使用WithMany。
publicclassBMap:EntityTypeConfiguration<B> { publicBMap() { this.HasKey(o=>o.Id); this.HasOptional(o=>o.A).WithMany(); } } 1:N(外键可空)
publicclassBMap:EntityTypeConfiguration<B> { publicBMap() { this.HasKey(o=>o.Id); this.Hasrequired(o=>o.A).WithMany(); } } 1:N(外键不可空)
如果A:B= 1:1,我们使用WithOptional。1:1的关联要求外键的非空和唯一,数据库是通过表B的外键作为主键来实现。
publicclassBMap:EntityTypeConfiguration<B> { publicBMap() { this.HasKey(o=>o.Id); this.Hasrequired(o=>o.A).WithOptional(); } }
四、可选导航属性
导航属性由关联类型决定,但其存在与否不会影响实体的依赖关系和关联类型。
对于B->A,如果A:B =1:N,我们可以在A中添加ICollection<B>类型的导航属性,同时修改关系配置,将该属性传递给WithMany方法。
publicclassA { publicintId{get;set;} publicICollection<B>BList{get;set;} } publicclassB { publicintId{get;set;} publicAA{get;set;} } publicclassAMap:EntityTypeConfiguration<A> { publicAMap() { this.HasKey(o=>o.Id); } } publicclassBMap:EntityTypeConfiguration<B> { publicBMap() { this.HasKey(o=>o.Id); this.HasOptional(o=>o.A).WithMany(o=>o.BList); } }
如果A:B =1:1,我们可以在A中添加B类型的导航属性,同时修改关系配置,将该属性传递给WithOptional方法。
publicclassA { publicintId{get;set;} publicBB{get;set;} } publicclassB { publicintId{get;set;} publicAA{get;set;} } publicclassAMap:EntityTypeConfiguration<A> { publicAMap() { this.HasKey(o=>o.Id); } } publicclassBMap:EntityTypeConfiguration<B> { publicBMap() { this.HasKey(o=>o.Id); this.Hasrequired(o=>o.A).WithOptional(o=>o.B); } }
五、显式外键属性
对于B->A,如果A:B =1:1,外键就是主键。
如果A:B =1:N,我们可以自定义导航属性对应的外键属性,首先在B中添加显式的用于外键的属性。
publicclassB { publicintId{get;set;} publicAA{get;set;} //publicintAId{get;set;} publicint?AId{get;set;} }
WithMany返回DependentNavigationPropertyConfiguration对象,我们使用该对象的HasForeignKey方法,如果实体联系配置为HasOptional,则需要使用可空类型匹配。
publicclassBMap:EntityTypeConfiguration<B> { publicBMap() { this.HasKey(o=>o.Id); this.HasOptional(o=>o.A).WithMany().HasForeignKey(o=>o.AId); } }
六、级联删除配置
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不应该存在重复的用户角色映射,因此使用外键作为联合主键。
publicclassUser { publicUser() { this.UserRoles=newList<UserRole>(); } publicintId{get;set;} publicstringUserName{get;set;} publicICollection<UserRole>UserRoles{get;set;} } publicclassRole { publicRole() { this.UserRoles=newList<UserRole>(); } publicintId{get;set;} publicstringRoleName{get;set;} publicICollection<UserRole>UserRoles{get;set;} } publicclassUserRole { publicUserUser{get;set;} publicintUserId{get;set;} publicRoleRole{get;set;} publicintRoleId{get;set;} } publicclassUserRoleMap:EntityTypeConfiguration<UserRole> { publicUserRoleMap() { 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); } }
2.节点树:
(1)确定依赖关系:Category自依赖,Category->Category
(2)配置依赖关系:Category可以单独存在,因此使用HasOptional。
(3)确定关联类型:Category:Category==1:*,因此使用WithMany。
(4)显式的外键属性:在UserRole中添加ParentId,由于Category可以单独存在,ParentId为可空类型。
(5)可选的导航属性:在Category中添加ICollection<Category>类型的导航属性。
publicclassCategory {publicintId{get;set;}publicstringName{get;set;}publicint?ParentId{get;set;}publicCategoryParent{get;set;}publicICollection<Category>Children{get;set;} }publicclassCategoryMap:EntityTypeConfiguration<Category>{publicCategoryMap() {this.HasKey(o=>o.Id);this.HasOptional(o=>o.Parent).WithMany(o=>o.Children).HasForeignKey(o=>o.ParentId); } }