在上一篇,大概介绍了Entity Framework Core关于关系映射的逻辑。在上一篇中留下了EF的外键映射没有说,也就是一对一,一对多,多对一,多对多的关系等。这一篇将为大家细细分析一下,如何设置这些映射。

1. 实体之间的关系

从数据表来考虑,两个表之前的关系有一对一,一对多(多对一)和多对多的关系。

其中一对一,指的是表A有一条记录对应着表B最多有一条记录与之对应。反过来也一样,表A也最多有一条记录与表B的某一条记录对应。具体在数据表上表现为,A表和B表各有一个外键指向对方。

一对多和多对一是一个概念,只是参考的方向是相反的。所谓的一对多就是其中多方上有一个属性或者列指向了另一个实体,而那个“一”的那头则没有对应的属性指向多方。

多对多是指两个类的实例各有一个集合属性指向对方,换句话说就是A有0到多个B,B也有0到多个A。这里有一个关于多对多的ER图。

2. 一对一关系

先给出两个示例类,为了方便理解,我只保留了主键和导航属性:

public class SingleModel
{
public int Id { get; set; }
public SingleTargetModel SingleTarget { get; set; }
} public class SingleTargetModel
{
public int Id { get; set; }
public SingleModel Single { get; set; }
}

那么我们开始写配置文件:

public class SingleModelConfig : IEntityTypeConfiguration<SingleModel>
{
public void Configure(EntityTypeBuilder<SingleModel> builder)
{
builder.ToTable("SingleModel");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
var relation = builder.HasOne(t => t.SingleTarget).WithOne(r => r.Single); }
} public class SingleTargeModelConfig : IEntityTypeConfiguration<SingleTargetModel>
{
public void Configure(EntityTypeBuilder<SingleTargetModel> builder)
{
builder.ToTable("SingleTargetModel");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
}
}

其中HasOne表示当前实体是关系中“一”,WithOne 表示导航目标类的关系。

当然,如果直接应用这两个配置到EF Context的话,在执行

Update-Database

会报以下错误:

The child/dependent side could not be determined for the one-to-one relationship between 'SingleModel.SingleTarget' and 'SingleTargetModel.Single'. To identify the child/dependent side of the relationship, configure the foreign key property. If these navigations should not be part of the same relationship configure them without specifying the inverse. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details.

意思就是无法定义一对一关系中的子/从属方

如何解决呢?之前在说的时候,EF会根据导航属性自动生成一个外键,但是这一条在一对一这里就有点不太起作用了。所以我们必须手动在导航属性的一侧实体类里配置外键,并用 HasForeignKey指定。(如果不使用Fluent API,也是需要在一端实体类配置外键,另一端则不需要)。

修改后:

public class SingleModel
{
public int Id { get; set; }
public int TargetId { get; set; }
public SingleTargetModel SingleTarget { get; set; }
}
public class SingleTargetModel
{
public int Id { get; set; }
public SingleModel Single { get; set; }
}

所以最终的配置应该如下:

public class SingleModelConfig : IEntityTypeConfiguration<SingleModel>
{
public void Configure(EntityTypeBuilder<SingleModel> builder)
{
builder.ToTable("SingleModel");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
builder.HasOne(t => t.SingleTarget).WithOne(r => r.Single).HasForeignKey<SingleModel>(t=>t.TargetId); }
} public class SingleTargeModelConfig : IEntityTypeConfiguration<SingleTargetModel>
{
public void Configure(EntityTypeBuilder<SingleTargetModel> builder)
{
builder.ToTable("SingleTargetModel");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
//builder.HasOne(t => t.Single).WithOne(r => r.SingleTarget).HasForeignKey<SingleTargetModel>("SingleId");
}
}

注意我注释的这一行,现在EF只在SingleModel表中生成了一个外键关系,在检索SingleTargetModel的时候,EF会从SingleModel表中检索对应的外键关系,并引入进来。

如果取消这行注释,EF会在SingleTargetModel表添加一个名为SingleId并指向SingleModel的外键,而取消SingleModel里的外键。

但是,这时候如果在SingleTargetModel里添加了一个非空属性的SingleId,SQLite插入数据时会报错。错误信息:

SQLite Error 19: 'FOREIGN KEY constraint failed'.

其他数据库提示,外键不能为空。

所以也就是说EF不推荐这种双方互导航的一对一关系。

这是生成的DDL SQL语句:

create table SingleModel
(
Id INTEGER not null
constraint PK_SingleModel
primary key autoincrement,
TargetId INTEGER not null
constraint FK_SingleModel_SingleTargetModel_TargetId
references SingleTargetModel
on delete cascade
); create unique index IX_SingleModel_TargetId
on SingleModel (TargetId); create table SingleTargetModel
(
Id INTEGER not null
constraint PK_SingleTargetModel
primary key autoincrement
);

3. 一对多或多对一

照例,先来两个类:

public class OneToManySingle
{
public int Id { get; set; }
public List<OneToManyMany> Manies { get; set; }
} public class OneToManyMany
{
public int Id { get; set; }
public OneToManySingle One { get; set; }
}

如果从OneToManySingle来看,这个关系是一对多,如果从OneToManyMany来看的话这个关系就是多对一。

那么我们看一下一对多的配置吧:

public class OneToManySingleConfig : IEntityTypeConfiguration<OneToManySingle>
{
public void Configure(EntityTypeBuilder<OneToManySingle> builder)
{
builder.ToTable("OneToManySingle");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
builder.HasMany(t => t.Manies)
.WithOne(p => p.One);
}
}
public class OneToManyManyConfig : IEntityTypeConfiguration<OneToManyMany>
{
public void Configure(EntityTypeBuilder<OneToManyMany> builder)
{
builder.ToTable("OneToManyMany");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
//builder.HasOne(p => p.One).WithMany(t=>t.Manies);
}
}

在使用隐式外键的时候,只需要设置导航属性的关联即可。如果想在Single端设置,需要先用 HasMany表示要设置一个多对X的关系,然后调用WithOne 表示是多对一。如果是Many端,则必须先声明是HasOne。

其中 WithXXX里的参数可以省略,如果只是配置了单向导航的话。

如果显示声明了外键,需要用HasForeignKey来标注外键。

以下是生成的DDL SQL语句:

create table OneToManySingle
(
Id INTEGER not null
constraint PK_OneToManySingle
primary key autoincrement
);
create table OneToManyMany
(
Id INTEGER not null
constraint PK_OneToManyMany
primary key autoincrement,
OneId INTEGER
constraint FK_OneToManyMany_OneToManySingle_OneId
references OneToManySingle
on delete restrict
); create index IX_OneToManyMany_OneId
on OneToManyMany (OneId);

4. 多对多

在讲多对多的时候,需要先明白一个概念。多对多,对于导航两端来说,是无法在自己身上找到对应的标记的。也就是说,各自的数据表不会出现指向对方的外键。那么,如何实现多对多呢?增加一个专门的中间表,用来存放两者之间的关系。

EF Core中取消了在映射关系中配置中间表的功能,所以在EF Core中需要一个中间表:

public class ManyToManyModelA
{
public int Id { get; set; }
public List<ModelAToModelB> ModelBs { get; set; }
}
public class ModelAToModelB
{
public int Id { get; set; }
public ManyToManyModelA ModelA { get; set; }
public ManyToManyModelB ModelB { get; set; }
}
public class ManyToManyModelB
{
public int Id { get; set; }
public List<ModelAToModelB> ModelAs { get; set; }
}

那么继续看一下配置文件:

public class ManyToManyToModelAConfig : IEntityTypeConfiguration<ManyToManyModelA>
{
public void Configure(EntityTypeBuilder<ManyToManyModelA> builder)
{
builder.ToTable("ManyToManyModelA");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
builder.HasMany(t => t.ModelBs).WithOne(p => p.ModelA);
}
} public class ManyToManyModelBConfig : IEntityTypeConfiguration<ManyToManyModelB>
{
public void Configure(EntityTypeBuilder<ManyToManyModelB> builder)
{
builder.ToTable("ManyToManyModelB");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
builder.HasMany(t => t.ModelAs).WithOne(p => p.ModelB);
}
}

与一对多的关系不同的地方是,这个需要两方都配置一个多对一的映射,指向中间表。

在EF 6中 中间表可以仅存在于关系中,但是在EF Core3 还没有这个的支持。也就是当前文章使用的版本。

5. 附加

在EF的外键约束中,导航属性是默认可空的。如果要求非空,也就是导航属性的另一端必须存在则需要在配置关系的时候添加:

IsRequired()

这个方法也用来声明字段是必须的。这个验证是在EF 调用 SaveChanges 的时候校验的。

6. 未完待续

照例的未完待续,下一篇将为大家介绍一下EF Core 在开发中的用法。

更多内容烦请关注我的博客《高先生小屋》

C# 数据操作系列 - 7. EF Core 导航属性配置的更多相关文章

  1. C# 数据操作系列 - 8. EF Core的增删改查

    0.前言 到目前为止,我们看了一下如何声明EF Core的初步使用,也整体的看了下EF Core的映射关系配置以及导航属性的配置. 这一篇,我带大家分享一下,我在工作中需要的EF Core的用法. 1 ...

  2. C# 数据操作系列 - 6 EF Core 配置映射关系

    0. 前言 在<C# 数据操作系列 - 5. EF Core 入门>篇中,我们简单的通过两个类演示了一下EF增删改查等功能.细心的小伙伴可能看了生成的DDL SQL 语句,在里面发现了些端 ...

  3. C# 数据操作系列 - 9. EF Core 完结篇

    0.前言 <EF Core>实际上已经可以告一段落了,但是感觉还有一点点意犹未尽.所以决定分享一下,个人在实际开发中使用EF Core的一些经验和使用的扩展包. 1. EF Core的异步 ...

  4. C# 数据操作系列 - 5. EF Core 入门

    0.前言 上一章简单介绍了一下ORM框架,并手写了一个类似ORM的工具类.这一章将介绍一个在C#世界里大名鼎鼎的ORM框架--Entity Framework的Core版. Entity Framew ...

  5. MVC3+EF4.1学习系列(五)----- EF查找导航属性的几种方式

    文章索引和简介 通过上一篇的学习 我们把demo的各种关系终于搭建里起来 以及处理好了如何映射到数据库等问题 但是 只是搭建好了关系 问题还远没有解决 这篇就来写如何查找导航属性 和查找导航属性的几种 ...

  6. 【ASP.NET Core】EF Core - “导航属性”

    “导航属性”是实体框架用得算是比较频繁的概念. 首先,它是类型成员,其次,他是属性,这不是 F 话,而是明确它的本质.那么,什么场景下会用到导航属性呢?重点就落在“导航”一词上了,当实体 A 需要引用 ...

  7. C# 数据操作系列 - 12 NHibernate的增删改查

    0. 前言 上一篇<C# 数据操作系列 - 11 NHibernate 配置和结构介绍> 介绍了Nhibernate里的配置内容.这一篇将带领大家了解一下如何使用NHIbernate.之前 ...

  8. C# 数据操作系列 - 19 FreeSql 入坑介绍

    0. 前言 前几天FreeSql的作者向我推荐了FreeSql框架,想让我帮忙写个文章介绍一下.嗯,想不到我也能带个货了.哈哈,开个玩笑-看了下觉得设计的挺有意思的,所以就谢了这篇文章. 简单介绍一下 ...

  9. C# 数据操作系列 - 15 SqlSugar 增删改查详解

    0. 前言 继上一篇,以及上上篇,我们对SqlSugar有了一个大概的认识,但是这并不完美,因为那些都是理论知识,无法描述我们工程开发中实际情况.而这一篇,将带领小伙伴们一起试着写一个能在工程中使用的 ...

随机推荐

  1. shiro:入门程序(一)

    SpringMVC项目 1:pom引入相关依赖 <dependencies> <!--测试依赖--> <dependency> <groupId>jun ...

  2. java IO流 之 字节流与字符流

    其实学习了file文件基础类,后面的字节流和字符流都特别简单了,首先需要知道字节流和字符流的区别 字节流: 用来传送图片.各种文件.大文件.文本都是通过字节流进行传输的. 字符流: 只能读取文本信息 ...

  3. [Asp.Net Core] Blazor Server Side 扩展用途 - 配合CEF来制作客户端浏览器软件

    前言 大家用过微信PC端吧? 这是用浏览器做的. 用过Visual Studio Code吧? 也是用浏览器做的. 听说, 暴雪客户端也包含浏览器核心?? 在客户端启动一个浏览器, 并不是什么难事了. ...

  4. 0day堆(1)堆的管理策略

    基本概念 堆块:堆区内存的基本单位 包括两个部分:块首,块身 块首:标识这个堆块自身的信息:如大小,是否被占用等 块身:分配给用户使用的数据区 堆表:一般位于堆区的起始位置,用于索引堆区所有堆块的信息 ...

  5. json格式的相互转化

    直接上代码: header("Content-type: text/html; charset=utf-8"); $arr = array(); $arr = [ ', ', ' ...

  6. 一些软件的 Basic Auth 行为

    一个 WBEM 在2003年的bug I'm trying to access the WBEM service of the CIMOM on the ESX Server 3i and all m ...

  7. innobackupex 出现Unrecognized character \x01; marked by

    centos 7.2 mysql 5.7.16 innobackupex version 2.4.6 [root@Devops-mysql-150-115 sh]# innobackupex --de ...

  8. 谈谈Java的线程池设计

    在实际项目中,如果因为想异步执行暂时性的任务而不断创建线程是很浪费资源的事情(当一个任务执行完后,线程也没用了).这种情况下,最好是将任务提交给线程池执行. 所谓池,就是将管理某一种资源,对资源进行复 ...

  9. nat和静态映射

    拓扑图: 实验要求: 1.R2.R3能访问外网的4.4.4.4(4.4.4.4为R4上的环回接口,用来模拟inter网). 2.R4访问222.222.222.100其实访问到的是内网的192.168 ...

  10. springBoot(6):web开发-模板引擎jsp

    一.新建工程 注意新建的工程下没有webapp目录eclipse下会自动创建webapp目录这里我们需要自动创建一个webapp目录并创建WEB-INF. 对ServletInitializer.ja ...