EF Core反向导航属性解决多对一关系

时间:2022-08-29 22:32:01

多对一是一种很常见的关系,例如:一个班级有一个学生集合属性,同时,班级有班长、语文课代表、数学课代表等单个学生属性,如果定义2个实体类,班级SchoolClass和学生Student,那么,班级SchoolClass类有多个学生Student类的导航属性,学生Student类有一个班级SchoolClass类的导航属性。此时就需要使用InverseProperty反向导航属性去指定通过哪个属性建立引用关系,否则数据库建不起来。

通过一个小DEMO做试验。

新建Asp.Net Core MVC网站项目,添加2个实体类如下所示

    //班级
public class SchoolClass
{
//主键
public int ID { get; set; } //班级名字
public string ClassTitle { get; set; } //本班级的学生集合
public List<Student> Students { get; set; } //班长
public Student ClassMonitor { get; set; } //语文课代表
public Student Chinese { get; set; } //数学课代表
public Student Mathematics { get; set; }
} //学生
public class Student
{
//主键
public int ID { get; set; } //姓名
public string Name { get; set; } //学生所在的班级
public SchoolClass MyClass { get; set; }
}

  

然后通过右键菜单添加SchoolClass实体类的控制器,让系统自动创建数据库上下文代码

EF Core反向导航属性解决多对一关系

然后会收到一个错误。

Unable to determine the relationship represented by navigation property 'SchoolClass.Students' of type 'List<Student>'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'. StackTrace:

系统无法判断SchoolClass多个Student导航属性的关系,此时可以在Students属性上面添加反向导航属性[InverseProperty("MyClass")],就可以完成自动化创建控制器了。

[InverseProperty("MyClass")]
public List<Student> Students { get; set; }

  

然后在软件启动时创建一组测试数据。

        public static void Main(string[] args)
{
//CreateWebHostBuilder(args).Build().Run(); IWebHost webHost = CreateWebHostBuilder(args).Build(); //系统初始化
AppInit(webHost.Services); webHost.Run();
} //系统初始化
private static void AppInit(IServiceProvider serviceProvider)
{
//初始化数据库
using (var scope = serviceProvider.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<StudentWebContext>(); //确保创建数据库
context.Database.EnsureCreated(); if (context.SchoolClass.Any())
return; var schoolClass61 = new SchoolClass()
{
ClassTitle = "六一班"
}; //先保存班级,否则报错
//Unable to save changes because a circular dependency was detected in the data to be saved: 'SchoolClass [Added] <- Students MyClass { 'MyClassID' } Student [Added] <- Chinese { 'ChineseID' } SchoolClass [Added]'.
context.Add(schoolClass61);
int rows = context.SaveChanges();
Console.WriteLine($"添加了班级{schoolClass61.ClassTitle}, 影响记录{rows}"); var student1 = new Student()
{
Name = "张三",
}; var student2 = new Student()
{
Name = "李四",
}; var student3 = new Student()
{
Name = "王五",
}; var student4 = new Student()
{
Name = "赵六",
}; schoolClass61.Students = new List<Student>()
{
student1,
student2,
student3,
student4
}; //设置同学的职位
schoolClass61.ClassMonitor = student1;
schoolClass61.Chinese = student2;
schoolClass61.Mathematics = student3; //保存到数据库
rows = context.SaveChanges();
Console.WriteLine($"添加了{schoolClass61.Students.Count}位同学, 影响记录{rows}"); }
}

  

然后修改控制器的Details方法,显示班级详细信息时Include加载全部学生集合Students,不需要再加载Chinese等各个课代表导航属性,因为已经加载了班上的全部学生,EF Core会自动处理这些Student类型的导航属性。

public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
} var schoolClass = await _context.SchoolClass
.Include(x => x.Students)
.FirstOrDefaultAsync(m => m.ID == id);
if (schoolClass == null)
{
return NotFound();
} return View(schoolClass);
}

  

修改Details页面显示班级学生和各个职务的学生。

<dt class="col-sm-2">
班上的同学
</dt>
<dd class="col-sm-10">
@foreach (var student in Model.Students)
{
@student.Name<br />
}
</dd>
<dt class="col-sm-2">
班长
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.ClassMonitor.Name)
</dd>
<dt class="col-sm-2">
语文课代表
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Chinese.Name)
</dd>
<dt class="col-sm-2">
数学课代表
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Mathematics.Name)
</dd>

  

运行成功。

EF Core反向导航属性解决多对一关系

打开数据库连接,可以查看系统自动创建的外键引用,完全符合预期。

CREATE TABLE [dbo].[Student] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (MAX) NULL,
[MyClassID] INT NULL,
CONSTRAINT [PK_Student] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_Student_SchoolClass_MyClassID] FOREIGN KEY ([MyClassID]) REFERENCES [dbo].[SchoolClass] ([ID])
); CREATE TABLE [dbo].[SchoolClass] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[ClassTitle] NVARCHAR (MAX) NULL,
[ClassMonitorID] INT NULL,
[ChineseID] INT NULL,
[MathematicsID] INT NULL,
CONSTRAINT [PK_SchoolClass] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_SchoolClass_Student_ChineseID] FOREIGN KEY ([ChineseID]) REFERENCES [dbo].[Student] ([ID]),
CONSTRAINT [FK_SchoolClass_Student_ClassMonitorID] FOREIGN KEY ([ClassMonitorID]) REFERENCES [dbo].[Student] ([ID]),
CONSTRAINT [FK_SchoolClass_Student_MathematicsID] FOREIGN KEY ([MathematicsID]) REFERENCES [dbo].[Student] ([ID])
);

  

继续试验,再增加一个老师实体类Teacher

    //老师
public class Teacher
{
//主键
public int ID { get; set; } //姓名
public string Name { get; set; } //老师作为班主任管理的班级
public SchoolClass AdminClass { get; set; }
}

  

给班级SchoolClass增加班主任、语文老师、数学老师属性

        //班主任
public Teacher HeadTeacher { get; set; } //语文老师
public Teacher ChineseTeacher { get; set; } //数学老师
public Teacher MathTeacher { get; set; }

  

修改Details方法,加载老师属性对象

            var schoolClass = await _context.SchoolClass
.Include(x => x.Students)
.Include(x => x.HeadTeacher)
.Include(x => x.ChineseTeacher)
.Include(x => x.MathTeacher)
.FirstOrDefaultAsync(m => m.ID == id);

  

修改Details页面增加显示老师

        <dt class="col-sm-2">
班主任
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.HeadTeacher.Name)
</dd>
<dt class="col-sm-2">
语文老师
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.ChineseTeacher.Name)
</dd>
<dt class="col-sm-2">
数学老师
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.MathTeacher.Name)
</dd>

  

补全StudentWebContext的数据表。

    public class StudentWebContext : DbContext
{
public StudentWebContext (DbContextOptions<StudentWebContext> options)
: base(options)
{
} public DbSet<SchoolClass> SchoolClass { get; set; } public DbSet<Student> Student { get; set; } public DbSet<Teacher> Teacher { get; set; } }

  

项目启动时增加老师的测试数据

                //添加老师
var teacher1 = new Teacher()
{
Name = "孔子"
}; var teacher2 = new Teacher()
{
Name = "李白"
}; var teacher3 = new Teacher()
{
Name = "祖冲之"
}; //设置老师的职位
schoolClass61.HeadTeacher = teacher1;
schoolClass61.ChineseTeacher = teacher2;
schoolClass61.MathTeacher = teacher3; //保存到数据库
rows = context.SaveChanges();
Console.WriteLine($"添加了老师同学, 影响记录{rows}");

  

打开VS2017的SQL Server对象管理器,通过右键菜单粗暴删除SchoolClass、Student数据表,再次运行项目,再次收到类似的错误

Unable to determine the relationship represented by navigation property 'SchoolClass.HeadTeacher' of type 'Teacher'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

参照上述方法,给班级SchoolClass的班主任属性HeadTeacher增加反向导航属性[InverseProperty("AdminClass")],这个问题就解决了。

        //班主任
[InverseProperty("AdminClass")]
public Teacher HeadTeacher { get; set; }

  

再次运行,会收到新的错误

The child/dependent side could not be determined for the one-to-one relationship between 'Teacher.AdminClass' and 'SchoolClass.HeadTeacher'. 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.

访问http://go.microsoft.com/fwlink/?LinkId=724062,自动跳转到https://docs.microsoft.com/zh-cn/ef/core/modeling/relationships#one-to-one,看介绍:

一对一

一对一关系两端具有引用导航属性。 它们遵循相同的约定作为一个对多关系,但在外键属性,以确保只有一个依赖于与每个主体上引入了唯一索引。

不好理解,有点绕?看示例的代码,大约是把其中一个实体类的导航属性改造为外键ID和导航属性相结合的方式。照办:

        public int AdminClassID { get; set; }

        //老师作为班主任管理的班级
public SchoolClass AdminClass { get; set; }

  

再次运行,可以创建数据库了,但是报错:

SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Teacher_SchoolClass_AdminClassID". The conflict occurred in database "StudentWebContext", table "dbo.SchoolClass", column 'ID'.

大意是AdminClassID属性不允许为空。看数据库设计器Teacher的代码,AdminClassID是非空的:

CREATE TABLE [dbo].[Teacher] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (MAX) NULL,
[AdminClassID] INT NOT NULL,
CONSTRAINT [PK_Teacher] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_Teacher_SchoolClass_AdminClassID] FOREIGN KEY ([AdminClassID]) REFERENCES [dbo].[SchoolClass] ([ID]) ON DELETE CASCADE

  

实际上,一位老师,是可以不担当任何一个班级的班主任的,因此AdminClassID属性应该是可空的。再改一下

        public int? AdminClassID { get; set; }

  

删除数据表,再次运行,没有任何问题了,数据库Teacher代码是正确的,

CREATE TABLE [dbo].[Teacher] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (MAX) NULL,
[AdminClassID] INT NULL,
CONSTRAINT [PK_Teacher] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_Teacher_SchoolClass_AdminClassID] FOREIGN KEY ([AdminClassID]) REFERENCES [dbo].[SchoolClass] ([ID])

  

Details页面数据显示也是正确的。

EF Core反向导航属性解决多对一关系

小结

EF Core多对一关系配置要点:

  1. A实体引用多个B导航属性,B实体引用一个A导航属性;
  2. A实体类注明其中一个B导航属性为InverseProperty;
  3. B实体类定义A导航属性的可空外键AID?;

代码:https://github.com/woodsun2018/StudentWeb

EF Core反向导航属性解决多对一关系的更多相关文章

  1. EF Code First 导航属性 与外键(转载)

    EF Code First 导航属性 与外键 一对多关系 项目中最常用到的就是一对多关系了.Code First对一对多关系也有着很好的支持.很多情况下我们都不需要特意的去配置,Code First就 ...

  2. ASP&period;NET EF 延迟加载,导航属性延迟加载

    ASP.NET EF 延迟加载,导航属性延迟加载   EF(EntityFramework)原理:属于ORM的一种实现 通过edmx文件来查看三部分:概念模型,数据模型,映射关系,上下文DbConte ...

  3. 用NHibernate处理带属性的多对多关系

    1.引言 老谭在面试开发者的时候,为了考察他们的数据库开发能力,经常祭出我的法宝,就是大学数据库教程中讲到的一个模式:学生选课.这个模式是这种: 在这个模式中,学生(Student)和课程(Cours ...

  4. ASP&period;NET Core EF 查询获取导航属性值,使用Include封装

    // 引用 using Microsoft.EntityFrameworkCore; // 摘要: // Specifies related entities to include in the qu ...

  5. EF Code First 导航属性 与外键

    一对多关系 项目中最常用到的就是一对多关系了.Code First对一对多关系也有着很好的支持.很多情况下我们都不需要特意的去配置,Code First就能通过一些引用属性.导航属性等检测到模型之间的 ...

  6. EF架构~为导航属性赋值时ToList&lpar;&rpar;的替换方案

    回到目录 今天在进行EF开发时,遇到一个问题,在进行join查询时,类中的一个集合类型的导航属性,在给它赋值时,将查询出来的结果ToList()后,出错了,linq to entity不支持这种操作, ...

  7. EF架构~过滤导航属性等,拼接SQL字符串

    拼接T-SQL串,并使它具有通用性 好处:与服务器建立一次连接,给服务器发一条SQL命令,即可实现 代码如下: 1 /// <summary> 2 /// 构建Insert语句串 3 // ...

  8. EF Core中Key属性相同的实体只能被跟踪(track)一次

    在EF Core的DbContext中,我们可以通过DbContext或DbSet的Attach方法,来让DbContext上下文来跟踪(track)一个实体对象,假设现在我们有User实体对象,其U ...

  9. EF——一对一、一对多、多对多关系的配置和级联删除 04(转)

    EF里一对一.一对多.多对多关系的配置和级联删除   本章节开始了解EF的各种关系.如果你对EF里实体间的各种关系还不是很熟悉,可以看看我的思路,能帮你更快的理解. I.实体间一对一的关系 添加一个P ...

随机推荐

  1. Android学习笔记——权限解释

    <!--允许读取电话状态SIM的权限--><uses-permission android:name="android.permission.READ_PHONE_STAT ...

  2. angular模板加载 ----ng-template

    Angularjs作为mvc(或者说mvvm)框架,同样具备模板这一基本概念. NG加载模板的顺序为 内存加载---AJAX加载. 如果排版乱掉,请查阅https://www.zybuluo.com/ ...

  3. OpenWrt刷机后LAN口无法连通的问题

    [路由器开发板硬件固件配置] MTK双频:MT7620a + MT7612e 内存:256 MB 闪存:16 MB 固件:MTK自带SDK中的OpenWrt固件(mtksdk-openwrt-2.6. ...

  4. 树莓派入门教程——使用Qt开发界面程序

    前言        Qt是一个1991年由奇趣科技开发的跨平台C++图形用户界面应用程序开发框架.它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器.Qt是面向对象的框架,使用特 ...

  5. 慕课linux学习笔记(四)常用命令(1)

    Root 表示当前登录用户 Localhost 主机名 ~ 当前所在位置(~表示/root) # 超级用户 $ 普通用户 命令 1.pwd 显示当前所在位置 2.ls 查询目录中的内容 -a 显示所有 ...

  6. laravel读取excel

    $filePath = 'storage/exports/成员信息.xls'; Excel::load($filePath, function ($reader) {// $data = $reade ...

  7. 关于PHP的那些坑

    因为PHP是弱类型语言,常常会发生许多意想不到的问题,所以,我们再次一一细数这些我们踏过的坑!!! 1) foreach中自动回将key为数值的转化成整型,造成无法匹配 function transl ...

  8. CC2530 Debug ---CC2530 无启动之32K晶振

    今天焊接CC2530,其中有个模块下载程序(协议栈程序),无法创建也无法加入网络. 第一步先检查32MH 晶振是否启动,用basice 程序看uart,发现可以正常打印log. 第二步,在线调试,看看 ...

  9. oracle学习之pl&sol;sql使用&equals;&equals;转载

    PLSQL循序渐进全面学习教程(全):https://blog.csdn.net/spark998/article/details/2065269

  10. Java虚拟机垃圾收集器与内存分配策略

    Java虚拟机垃圾收集器与内存分配策略 概述 那些内存须要回收,什么时候回收.怎样回收是GC须要完毕的3件事情. 程序计数器.虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性 ...