asp.net core系列 26 EF模型配置(实体关系)
一.概述
ef实体关系定义了两个实体互相关联起来(主体实体和依赖实体的关系,对应数据库中主表和子表关系)。 在关系型数据库中,这种表示是通过外键约束来体现。本篇主要讲一对多的关系。先了解下描述关系的术语。
(1) 依赖实体: 这是包含外键属性的实体(子表)。有时称为 child 。
(2) 主体实体: 这是包含主/备用键属性的实体(主表)。 有时称为 parent。
(3) 外键:依赖实体(子表)中的属性,用于存储主表的主键属性的值。
(4) 主键: 唯一标识的主体实体(主表)的属性。 这可能是 primary key 或备用键。
(5) 导航属性: 包含对相关实体引用,在的主体和或依赖实体上定义的属性。
集合导航属性: 一个导航属性,对多个相关实体的引用。
引用导航属性: 一个导航属性,对单个相关实体的引用。
下面示例代码来说明blog和post之间的一对多关系。其中 post
是依赖实体;blog
是主体实体;post.blogid
是外键;blog.blogid
是主键;post.blog引用导航属性;blog.posts集合导航属性。
public class blog { public int blogid { get; set; } public string url { get; set; } public list<post> posts { get; set; } } public class post { public int postid { get; set; } public string title { get; set; } public string content { get; set; } public int blogid { get; set; } public blog blog { get; set; } }
二.约定
按照约定,当在实体类型上发现导航属性时,将创建关系。如果属性所指向的类型不能被当前数据库提供程序映射为标量类型,则将其视为导航属性。下面用三个示例来说明通过约定创建的主从实体关系。
2.1 完全定义的关系
关系最常见的模式是在关系的两端定义导航属性,并在依赖实体类中定义外键属性。
(1)如果两个类型之间找到一对导航属性,则它们将被配置为同一关系的反转导航属性。
(2)如果依赖实体包含名为的属性<primary key property name>
, <navigation property name><primary key property name>
,或<principal entity name><primary key property name>
然后它将配置为外键。
下面代码示例是完全定义的关系的实体,通过约定来创建主从实体关系。
public class blog { public int blogid { get; set; } public string url { get; set; } //反转导航属性 public list<post> posts { get; set; } } public class post { public int postid { get; set; } public string title { get; set; } public string content { get; set; } //通过约定(<primary key property name>)创建外键 public int blogid { get; set; } //反转导航属性 public blog blog { get; set; } }
使用ef基于数据模型(blog和post实体)创建数据库。生成后,查看数据表的关系映射,如下图所示:
2.2 没有外键属性
虽然建议在依赖实体类中定义外键属性,但这不是必需的。如果没有找到外键属性,那么将引入一个名为<navigation property name> > principal key property name>的隐藏外键属性(上篇有介绍)。在下面代码中,依赖实体post中没有显示定义外键blogid。
public class post { public int postid { get; set; } public string title { get; set; } public string content { get; set; } public blog blog { get; set; } }
2.3 单一导航属性
仅包含一个导航属性(没有反转导航属性,也没有外键属性)就足以拥有约定定义的关系。还可以有一个导航属性和一个外键属性。代码如下所示产:
public class blog { public int blogid { get; set; } public string url { get; set; } public list<post> posts { get; set; } } public class post { public int postid { get; set; } public string title { get; set; } public string content { get; set; } }
三. 数据注释
有两个数据注释可用于配置关系,[foreignkey]和[inverseproperty]。
3.1 foreignkey可以将指定属性设置为外键属性。 这种设置通常是外键属性不被约定发现时,显示设置。如下面代码示例, 在依赖实体post中将blogforeignkey指定为外键。代码中生成的主从实体关系与上面的约定示例是一样的。
public class blog { public int blogid { get; set; } public string url { get; set; } public list<post> posts { get; set; } } public class post { public int postid { get; set; } public string title { get; set; } public string content { get; set; } public int blogforeignkey { get; set; } [foreignkey("blogforeignkey")] public blog blog { get; set; } }
下面使用ef基于数据模型(blog和post实体)创建数据库。生成后,查看数据表的关系映射,如下图所示:
3.2 inverseproperty配置依赖实体和主体实体上的导航属性如何配对。当两个实体类型之间有一对以上的导航属性时,通常会这样做。如下面代码示例:
public class post { public int postid { get; set; } public string title { get; set; } public string content { get; set; } public blog blog { get; set; } public user author { get; set; } public user contributor { get; set; } } public class user { public string userid { get; set; } public string firstname { get; set; } public string lastname { get; set; } [inverseproperty("author")] public list<post> authoredposts { get; set; } [inverseproperty("contributor")] public list<post> contributedtoposts { get; set; } }
下面使用ef基于数据模型(user和post实体)创建数据库。生成后,查看数据表的关系映射,如下图所示:
四. fluent api
要在fluent api中配置关系,首先要确定组成关系的导航属性。hasone或hasmany标识开始配置的实体类型上的导航属性。然后调用withone
或withmany
来标识反导航。hasone/withone用于引用导航属性,而hasmany/withmany用于集合导航属性。
在ef基于现有数据库进行反向工程时,根据数据库将自动生成dbcontext上下文类,里面重写了onconfiguring方法。下面示例是一个mycontext上下文类,在onmodelcreating方法中确定了实体的关系。
4.1 完全定义的关系
class mycontext : dbcontext { public dbset<blog> blogs { get; set; } public dbset<post> posts { get; set; } protected override void onmodelcreating(modelbuilder modelbuilder) { modelbuilder.entity<post>() .hasone(p => p.blog) //post类有一个blog引用导航属性 .withmany(b => b.posts);//blog类有一个posts反导航集合 } } public class blog { public int blogid { get; set; } public string url { get; set; } //反导航集合 public list<post> posts { get; set; } } public class post { public int postid { get; set; } public string title { get; set; } public string content { get; set; } //引用导航属性 public blog blog { get; set; } }
4.2 单一导航属性
如果只有一个导航属性,那么就会出现无参数重载的withone以及withmany。这表明在关系的另一端有一个概念上的引用或集合,但是实体类中不包含导航属性。
class mycontext : dbcontext { public dbset<blog> blogs { get; set; } public dbset<post> posts { get; set; } protected override void onmodelcreating(modelbuilder modelbuilder) { modelbuilder.entity<blog>() .hasmany(b => b.posts)//blog类有一个集合导航属性 .withone(); } } public class blog { public int blogid { get; set; } public string url { get; set; } public list<post> posts { get; set; } } public class post { public int postid { get; set; } public string title { get; set; } public string content { get; set; } }
下面使用ef基于数据模型(user和post实体)创建数据库。生成后,查看数据表的关系映射,如下图所示:
4.3 外键
可以使用 fluent api 配置哪些属性应用作给定关系外键属性。
class mycontext : dbcontext { public dbset<blog> blogs { get; set; } public dbset<post> posts { get; set; } protected override void onmodelcreating(modelbuilder modelbuilder) { modelbuilder.entity<post>() .hasone(p => p.blog) .withmany(b => b.posts) .hasforeignkey(p => p.blogforeignkey); } } public class blog { public int blogid { get; set; } public string url { get; set; } public list<post> posts { get; set; } } public class post { public int postid { get; set; } public string title { get; set; } public string content { get; set; } public int blogforeignkey { get; set; } public blog blog { get; set; } }
以下代码列表演示如何配置复合外键。
class mycontext : dbcontext { public dbset<car> cars { get; set; } protected override void onmodelcreating(modelbuilder modelbuilder) { modelbuilder.entity<car>() .haskey(c => new { c.state, c.licenseplate }); modelbuilder.entity<recordofsale>() .hasone(s => s.car) .withmany(c => c.salehistory) .hasforeignkey(s => new { s.carstate, s.carlicenseplate }); } } public class car { public string state { get; set; } public string licenseplate { get; set; } public string make { get; set; } public string model { get; set; } public list<recordofsale> salehistory { get; set; } } public class recordofsale { public int recordofsaleid { get; set; } public datetime datesold { get; set; } public decimal price { get; set; } public string carstate { get; set; } public string carlicenseplate { get; set; } public car car { get; set; } }
总结:关于实体关系,还讲到了“必需和可选的关系”、“级联删除”。以及关系模式中的“一对一关系”、“多对多关系",这些以后用到再参考文档。 个人认为在传统开发中,以建库建表优先的情况下,不会去设置数据表的外键关系,这种关系是由编程去控制。 这样对数据库进行反向工程时,也不会生成有关系的主从实体模型。
参考文献:
官方文档:ef 实体关系
推荐阅读
-
asp.net core 系列 20 EF基于数据模型创建数据库
-
asp.net core系列 29 EF模型配置(查询类型,关系数据库建模)
-
asp.net core系列 23 EF模型配置(概述, 类型和属性的包含与排除)
-
asp.net core系列 26 EF模型配置(实体关系)
-
asp.net core系列 28 EF模型配置(字段,构造函数,拥有实体类型)
-
asp.net core系列 25 EF模型配置(隐藏属性)
-
asp.net core系列 27 EF模型配置(索引,备用键,继承)
-
asp.net core 系列 20 EF基于数据模型创建数据库
-
(21)ASP.NET Core EF创建模型(关系)
-
asp.net core系列 23 EF模型配置(概述, 类型和属性的包含与排除)