龙空技术网

一文带你了解EF Core关系配置 | C# 数据操作系列

程序员小高学习笔记 317

前言:

当前小伙伴们对“ef添加数据”都比较关切,同学们都想要剖析一些“ef添加数据”的相关资讯。那么小编在网上网罗了一些关于“ef添加数据””的相关内容,希望大家能喜欢,各位老铁们一起来学习一下吧!

在上一篇,大概介绍了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 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 在开发中的用法。

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

标签: #ef添加数据 #ef添加数据常见错误 #ef添加数据常见错误是什么