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表的主键)。

推荐的写法:

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

摒弃的写法:

public class AMap : EntityTypeConfiguration<A> { public AMap() { 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表中生成非空的外键。

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

HasRequired

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

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

HasOptional

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

四、配置关联类型

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(外键可空)

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

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

1:N(外键不可空)

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

如果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

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

四、可选导航属性

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

对于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);
}
}

导航属性

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);
}
}

如果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);
}
}

导航属性

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);
}
}

五、显式外键属性

对于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; }
}

显式外键

public class B
{
public int Id { get; set; } public A A { get; set; } //public int AId { get; set; }
public int? AId { get; set; }
}

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);
}
}

外键配置

public class BMap : EntityTypeConfiguration<B>
{
public BMap()
{
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不应该存在重复的用户角色映射,因此使用外键作为联合主键。

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

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);
}
}

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

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);
}
}

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. HDU-4417 Super Mario,划分树+二分!

    Super Mario 这个题也做了一天,思路是很清晰,不过二分那里写残了,然后又是无限RE.. 题意:就是查询区间不大于k的数的个数. 思路:裸划分树+二分答案.将区间长度作为二分范围.这个是重点. ...

  2. BZOJ 4318 OSU! ——期望DP

    这次要求$x^3$的概率和. 直接维护三个值$x$ $x^2$ $x^3$的期望. 概率的平方不等于平方的概率. #include <map> #include <ctime> ...

  3. [BZOJ4776] [Usaco2017 Open]Modern Art(差分 + 思维?)

    传送门 可以预处理出每种颜色的上下左右的位置,这样就框出来了一个个矩形,代表每种颜色分别涂了哪里. 然后用二维的差分. 就可以求出来每个位置至少涂了几次,如果 > 1 的话,就肯定不是先涂的, ...

  4. python 写excal

           workbook.save(filename)

  5. P3799 妖梦拼木棒 (组合数学)

    题目背景 上道题中,妖梦斩了一地的木棒,现在她想要将木棒拼起来. 题目描述 有n根木棒,现在从中选4根,想要组成一个正三角形,问有几种选法? 输入输出格式 输入格式: 第一行一个整数n 第二行n个整数 ...

  6. 解决mybatis xml文件代码提示

    今天在开源项目社区看到一个插件挺不错的,推荐给大家.叫MyBatis MyBatis 是一个流行的 Java 数据持久层框架.MyBatipse 是一个 Eclipse 插件,在编辑 MyBatis ...

  7. cf3D Least Cost Bracket Sequence

    This is yet another problem on regular bracket sequences. A bracket sequence is called regular, if b ...

  8. msp430项目编程45

    msp430综合项目---蓝牙控制系统45 1.电路工作原理 2.代码(显示部分) 3.代码(功能实现) 4.项目总结

  9. Laravel 控制器的request

    public function request1(Request $request){ //取值 $name = Request::input('name'); //是否有值 if($request- ...

  10. Ubuntu下Deb软件包相关安装与卸载

    安装deb软件包 sudo dpkg -i xxx.deb 删除软件包 sudo dpkg -r xxx.deb 连同配置文件一起删除 sudo dpkg -r --purge xxx.deb 查看软 ...