Entity Framework 6 Code First 实践系列1:实体类配置-根据依赖配置关系和关联

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的配置文件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的配置文件,你甚至不肯定修改了类A的配置文件是会引起A表的变化。

三、配置依赖关系

配置文件的基类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);
}
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结