C# 数据操作系列 - 7. EF Core 导航属性配置_ef core导航属性的插入顺序_小高同学的博客-程序员秘密

技术标签: C# 从入门到开发  

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

1. 实体之间的关系

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

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

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

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

image-20200515220140873

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 在开发中的用法。

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

file

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/c7jie001/article/details/106172951

智能推荐

python面试题问100题_110道Python面试题(下)_号三的博客-程序员秘密

临时重定向,POST不会变成GET400 Bad Request请求报文语法错误或参数错误401 Unauthorized需要通过HTTP认证,或认证失败403 Forbidden请求资源被拒绝404 Not Found无法找到请求资源(服务器无理由拒绝)500 Internal Server Error服务器故障或Web应用故障503 Service Unavailable服务器超负载或停机维护...

MinGW的安装_mingw 装哪几个_想fly的朱的博客-程序员秘密

<br />MINGW的介绍见http://www.mingw.org/,也请同学们自己在百度搜一下,有大量的资料,这里我就不多说了。<br />1.1下载<br />在MINGW主页上即可找到mingw 5.02的下载地址,这里提供一个<br />http://heanet.dl.sourceforge.net/sourceforge/mingw/MinGW-5.0.2.exe<br />不能上国外网的同学请找代理,或者在百度搜,国内有很多地方提供下载。<br /><b

vlookup 2张表 显示na_【Excel 函数】Vlookup 正反向查询_weixin_39825941的博客-程序员秘密

函数Vlookup是日常工作中常见的函数。它的本职工作是帮助你查询(返回)所需要的一个或者多个值。【注意事项与适用范围】 以单靠Vlookup函数为例,对数据区域有一定的要求 1.数据区域内不能出现错误值或者空值 2.数据区域内不能用合并同类项 3.目的数值所在列...

第一章 java 基础 - 06 深入理解 Java 泛型_YWLi的博客-程序员秘密

无意间都到一篇《走心的安卓工程师跳槽经验分享》,发现自己工作几年了,技术方面虽然有了飞跃的进步,可是不知道自己的技术到了什么地步,每个方面我都涉及到了,但都不深,这大概是初级工程师的诟病吧!即使知道也不知道从何下手,非常感谢《走心的安卓工程师跳槽经验分享》的作者!感兴趣的朋友和我一起走下去吧!06 深入理解 Java 泛型最近我的 Effective Java 也刚好编写到泛型,有...

绝对干货!初学者也能看懂的DPDK解析_Linux阅码场的博客-程序员秘密

转载来源:博客园原文链接:https://www.cnblogs.com/qcloud1001/p/9585724.html一、网络IO的处境和趋势从我们用户的使用就可以...

12 年!Android 系统的漫漫设计路_云布道师的博客-程序员秘密

凌云时刻Android 1.0(2008年)Android操作系统的历史很悠久。2008年9月,第一款Android手机HTC Dream上市。虽然如今我们很难找到这款手机,但Google...

随便推点

JSP--TOMCAT-MYSQL web页面删除_weixin_34071713的博客-程序员秘密

deleteStudentjsp.jsp页面代码&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=GB2312&quot; pageEncoding=&quot;UTF-8&quot;%&amp;gt;&amp;lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; &quot;http

centos7 安装shutter 截屏 工具_centos7 有什么截图工具吗_Man_In_The_Night的博客-程序员秘密

shutter是linux中的一个常用的截屏工具实验环境:centos7.61 安装Nux Dextop库sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm检查Nux Dextop是否安装成功:sudo yum rep...

力扣刷题记录#数组#简单#628三个数的最大乘积_阿刷的博客-程序员秘密

题目描述给定一个整型数组,在数组中找出由三个数组成的最大乘积,并输出这个乘积。示例输入: [1,2,3]输出: 6输入: [1,2,3,4]输出: 24解答列表里的数有三种情况:1.全为负数,包括02.全为正数,包括03.正负都有,包括0将数组排序,最大的乘积有两种情况:1.两个最小负数(绝对值最大)+最大正数2.三个最大正数class Solution(object...

安卓应用如何引用动态库_Master Cui的博客-程序员秘密

在APP的build.gradle中添加如下代码release { jniLibs.srcDirs += ['../../../../lib/android/release'] }其中后面的是一个相对路径,根据动态库的位置自行修改添加完之后,gradle文件是这样的注意安卓的路径中千万不能有空格上述只是添加了一个release版本的动态库,debug版本的动态库也是同理...

高德地图js 开发五:输入地名跳转到该位置_欧巴酱的博客-程序员秘密

InitMap(11, [114.312151, 30.58454]);//默认武汉中心坐标var map; //地图初始化 function InitMap(zoom, arr) { map = new AMap.Map('container', { zoom: zoom,//级别 center: arr,//中心点坐标 viewMode: '3D'/.

codeforces365B_秋天的风--的博客-程序员秘密

#includeint main() {int a,b,c,n,sum,max,i;while(scanf("%d",&n)!=EOF) {if(nscanf("%d",&a);printf("1\n");continue;}scanf("%d%d",&a,&b);sum=2;max=2;for(i=3;iscanf("%d",&c);if(c=