- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
上次老周扯了有关主、从实体的话题,本篇咱们再挖一下,主、从实体之间建立的关系,跟咱们常用的一对1、一对多这些关系之间有什么不同.
先看看咱们从学习数据库开始就特熟悉的常用关系——多对多、一对1、一对多说起。数据实体之间会建立什么样的关系,并不是规则性的,而是要看数据的功能。比如你家养的狗狗和水果(你家狗狗可能不吃水果,但老周养的动物基本是什么都吃的,因为从它们幼年起,老周就训练它们,对食物要来者不拒,就算哪天它们不想跟着老周混,出去流浪也不会饿死,适应性更强).
假设:
1、你的数据是以狗狗为主,那么一条狗狗会吃多种水果。即狗狗对水果是一对多; 。
2、你的数据以水果为主,每种水果单独记录,然后在另一个表中记录水果被哪几条狗喜欢。例:雪梨,狗Y和狗B都喜欢吃。于是水果对狗狗也可以是一对多的关系.
再假设你有个幼儿园学生尿床登记表,表中记录每次尿床的时间、床号等。每一条尿床记录都有一个字段,引用自学生表,代表是哪们同学尿床了。多条尿床记录可能都是同一个人的,比如,小明一周有三次尿床。这样,尿床记录和学生之间可以是多对一关系了.
数据是为咱们人服务的,因此实体之间建立什么样的关系,得看咱们人类是怎么理解,以及这些实体的用途.
还是用上一篇水文中的学生 - 作业的例子.
public class Student { // 主键:学生ID public int StuID { get ; set ; } // 学生姓名 public string ? Name { get ; set ; } // 年级 public ushort Grade { get ; set ; } // 作业(导航属性) public IEnumerable<Homework> Homeworks { get ; set ; } = new List<Homework> (); } public class Homework { // 主键,ID public int WorkID { get ; set ; } // 作业描述 public string ? Description { get ; set ; } // 科目(导航属性) public Subject? Subject { get ; set ; } // 引用学生对象 public Student? Student { get ; set ; } } public class Subject { // 主键:科目ID public int SubID { get ; set ; } // 科目名称 public string ? Name { get ; set ; } }
这次老周加了个实体——Subject,它表示作业的科目(数学、语文等).
导航属性是用于建立实体关系的.
1、学生类中,Homeworks 属性建立与 Homework 对象的关系:一条学生信息可以对应多条作业信息,是一对多的关系; 。
2、作业类中,Subject 属性建立与 Subject 对象的关系。一对一的关系.
在 DbContext 的自定义类型中,三个实体间的关系配置如下:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 设置主键 modelBuilder.Entity<Student>().HasKey(s => s.StuID); modelBuilder.Entity <Homework>().HasKey(w => w.WorkID); modelBuilder.Entity <Subject>().HasKey(u => u.SubID); // 建立模型关系 modelBuilder.Entity<Student>().HasMany(s => s.Homeworks).WithOne(w => w.Student); modelBuilder.Entity <Homework>().HasOne(w => w.Subject); }
这是咱们常规的关系配置方法,从当前实体到另一实体的关系描述为 HasXXX 方法;HasXXX 方法调用后,会顺带调用一个 WithXXX 方法。WithXXX 方法是反向描述,即描述另一个实体与当前实体的关系。这样调用可以建立比较完整的相对关系.
在上述代码中,Student -> Homework 是一对多,所以,Student 实体上调用 HasMany 方法;之后是反向关系,Homework -> Student 是一对一关系,也就是说,一条 Homework 记录通过外键只引用一条学生记录。因此调用了 WithOne 方法.
Homework -> Subject 是一对一,所以在 Homework 实体上调用 HasOne 方法。这里,Homework 与 Subject 两实体并没有建立相互引用的关系,仅仅是作业中引用了科目信息,而 Subject 实体自身可以独立,它不需要引用 Homework 的任何实例,因此没有调用 WithXXX 方法.
由于实体之间建立的关系是相对的,即参照当前对象。所以,上面代码也可以这样写:
modelBuilder.Entity<Homework>().HasOne(h => h.Student).WithMany(s =>
s.Homeworks);
modelBuilder.Entity
<Homework>().HasOne(h => h.Subject);
要注意的是, 这两种关系配置其实是相同的,所以两者任选一即可 ,不要重复配置.
两种关系配置的差别就在选择谁来做“当前实体”,即以当前实体为参照而建立相对关系。第二种方法是以 Homework 实体为当前实体,一条作业信息只关联一位学生,所以是一对一,调用 HasOne 方法;反过来,一条学生信息可包含多条作业信息,所以是一对多,即调用 WithMany 方法.
定义几个静态方法,用于验证模型建得对不对.
首先,InitDatabase 方法负责运行阶段创建数据库,并插入一些测试数据.
static void InitDatabase() { using MyContext cxt = new (); // 确保数据已创建 bool v = cxt.Database.EnsureCreated(); // 如果数据库已存在,不用初始化数据 if (! v) return ; /* 初始化数据 */ // 这是科目 Subject s1 = new (){ Name = " 语文 " }; Subject s2 = new (){ Name = " 数学 " }; Subject s3 = new (){ Name = " 英语 " }; Subject s4 = new (){ Name = " 物理 " }; Subject s5 = new (){ Name = " 地理 " }; cxt.Subjects.AddRange( new []{ s1, s2, s3, s4, s5 }); // 学生和作业可以一起添加 cxt.Students.Add( new Student{ Name = " 小华 " , Grade = 4 , Homeworks = new [] { new Homework { Description = " 背单词3500个 " , Subject = s3 }, new Homework { Description = " 作文《我是谁,我在哪里》 " , Subject = s1 }, new Homework { Description = " 手绘广州地铁网络图 " , Subject = s5 } } } ); cxt.Students.Add( new Student { Name = " 王双喜 " , Grade = 3 , Homeworks = new [] { new Homework { Description = " 完型填空练习 " , Subject = s3 } } } ); cxt.Students.Add( new Student { Name = " 割麦小王子 " , Grade = 5 , Homeworks = new []{ new Homework { Description = " 实验:用激光给蟑螂美容 " , Subject = s4 }, new Homework{ Description = " 翻译文言文《醉驾通鉴》 " , Subject = s1 } } } ); // 保存到数据库 cxt. SaveChanges (); }
SaveChanges 方法记得调用,调用了才会保存数据.
ShowData 方法负责在控制台打印数据.
static void ShowData() { using MyContext ctx = new (); var students = ctx.Students. Include (s => s.Homeworks) . ThenInclude (hw => hw.Subject) . AsEnumerable (); // 打印学生信息 Console.WriteLine( " {0,-5}{1,-10}{2,-6} " , " 学号 " , " 姓名 " , " 年级 " ); Console.WriteLine( " ---------------------------------------------------- " ); foreach ( var stu in students) { Console.WriteLine($ " {stu.StuID,-7}{stu.Name,-10}{stu.Grade,-4} " ); // 打印作业信息 foreach (Homework wk in stu.Homeworks) { Console.Write( " >> {0,-4} " , wk.Subject! .Name); Console.WriteLine(wk.Description); } Console.Write( " \n\n " ); } }
在加载数据时得小心,因为如果你只访问 Students 集合,那么,Homeworks 和 Subjects 集合不会加载,这会使得 Student 实体的 Homeworks 属性变为空。为了让访问 Students 集合时同时加载关联的数据,要用 Include 方法.
第一个 Include 方法加载 Homeworks 属性引用的 Homework对象;第二个ThenInclude 方法是指在加载 Homework 后,Homework 实体的 Subject 属性引用了 Subject 对象,所以 ThenInclude 方法是通知模型顺便加载 Subjects 集合.
最后,要调用一下实际触发查询的方法,如 AsEnumerable 方法,这样才会让查询执行,你在内存中才能访问到数据。当然,像 ToArray、ToList 之类的方法也可以,这个和 LINQ 语句的情况类似。要调用到相应的方法才触发查询真正执行.
RemoveDatabase 方法是可选的,删除数据库。咱们这是演示,免得在数据库中存太多不必要的东西。测试完代码可以调用一下它,删除数据库。这里老周照例用 SQL Server LocalDB 来演示.
static void RemoveDatabase() { using MyContext c = new (); c.Database.EnsureDeleted(); }
------------------------------------------------------------------------------------------- 。
用的时候,按顺调用这些方法,就可以测试了.
Console.WriteLine( " ** 第一步:初始化数据库。【请按任意键继续】 " ); _ = Console.ReadKey( true ); InitDatabase(); Console.WriteLine( " ** 第二步:显示数据。【请按任意键继续】 " ); _ = Console.ReadKey( true ); ShowData(); // Console.WriteLine("** 第三步:删除数据库。【请按任意键继续】"); // _ = Console.ReadKey(); // RemoveDatabase();
产生的数据表如下图所示:
。
我们上面的这个模型还是有点问题的,可以看一下,生成的数据表是没有删除约束的.
CREATE TABLE [ dbo ] . [ Homeworks ] ( [ WorkID ] INT IDENTITY ( 1 , 1 ) NOT NULL , [ Description ] NVARCHAR ( MAX ) NULL , [ SubjectSubID ] INT NULL , [ StudentStuID ] INT NULL , CONSTRAINT [ PK_Homeworks ] PRIMARY KEY CLUSTERED ( [ WorkID ] ASC ), CONSTRAINT [ FK_Homeworks_Students_StudentStuID ] FOREIGN KEY ( [ StudentStuID ] ) REFERENCES [ dbo ] . [ Students ] ( [ StuID ] ), CONSTRAINT [ FK_Homeworks_Subjects_SubjectSubID ] FOREIGN KEY ( [ SubjectSubID ] ) REFERENCES [ dbo ] . [ Subjects ] ( [ SubID ] ) );
假如现在我要删掉一条学生记录.
using (MyContext dbcontext = new ()) { // 删第一条记录 var one = dbcontext.Students.FirstOrDefault(); if (one != null ) { dbcontext.Students.Remove(one); dbcontext.SaveChanges(); } }
但删除的时候会遇到错误.
这表明咱们要配置级联删除.
public class MyContext : DbContext { public DbSet<Student> Students => Set<Student> (); public DbSet<Homework> Homeworks => Set<Homework> (); public DbSet<Subject> Subjects => Set<Subject> (); protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer( @" server=(localdb)\MSSQLLocalDB;Database=TestDB;Integrated Security=True " ); } protected override void OnModelCreating(ModelBuilder modelBuilder) { …… // 建立模型关系 modelBuilder.Entity<Student> () .HasMany(s => s.Homeworks) .WithOne(w => w.Student) .OnDelete(DeleteBehavior.Cascade) ; modelBuilder.Entity <Homework>().HasOne(w => w.Subject); } }
现在再删一次看看.
可以看到,与第一位学生有关的作业记录也一并被删除了。生成的数据表也与前面有一点差异.
CREATE TABLE [ dbo ] . [ Homeworks ] ( [ WorkID ] INT IDENTITY ( 1 , 1 ) NOT NULL , [ Description ] NVARCHAR ( MAX ) NULL , [ SubjectSubID ] INT NULL , [ StudentStuID ] INT NULL , CONSTRAINT [ PK_Homeworks ] PRIMARY KEY CLUSTERED ( [ WorkID ] ASC ), CONSTRAINT [ FK_Homeworks_Students_StudentStuID ] FOREIGN KEY ( [ StudentStuID ] ) REFERENCES [ dbo ] . [ Students ] ( [ StuID ] ) ON DELETE CASCADE , CONSTRAINT [ FK_Homeworks_Subjects_SubjectSubID ] FOREIGN KEY ( [ SubjectSubID ] ) REFERENCES [ dbo ] . [ Subjects ] ( [ SubID ] ) );
约束里面显然多了 ON DELETE CASCADE 语句.
回忆一下,在上一篇水文中,咱们使用主从对象后,我们在模型中没有明确配置级联删除,但生成的数据表中自动加上级联删除了.
这是不是说明:主从关系的实体对象里,主实体对从属实体的控制更强烈,咱们再对比对比看.
现在,让 Student 和 Homework 成为主从关系.
public class MyContext : DbContext { public DbSet<Student> Students => Set<Student> (); public DbSet<Homework> Homeworks => Set<Homework> (); public DbSet<Subject> Subjects => Set<Subject> (); protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { …… } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 设置主键 modelBuilder.Entity<Student>().HasKey(s => s.StuID); modelBuilder.Entity <Subject>().HasKey(u => u.SubID); // 建立模型关系 modelBuilder.Entity<Student> () . OwnsMany(s => s.Homeworks, mrb => { mrb.WithOwner(w => w.Student); mrb.HasKey(w => w.WorkID); mrb.HasOne(w => w.Subject); }); } }
上次我们也证实过,凡成为从属的实体是无法单独进行配置的(如主键等),只能在配置主从关系的时候通过 OwnsMany 方法的委托来配置.
主从关系会自动生成级联删除语句.
CREATE TABLE [ dbo ] . [ Homeworks ] ( ……, CONSTRAINT [ PK_Homeworks ] PRIMARY KEY CLUSTERED ( [ WorkID ] ASC ), CONSTRAINT [ FK_Homeworks_Students_StudentStuID ] FOREIGN KEY ( [ StudentStuID ] ) REFERENCES [ dbo ] . [ Students ] ( [ StuID ] ) ON DELETE CASCADE , …… );
还有一点更关键的,Homework 成为 Student 的从对象后,你甚至无法直接访问 Homeworks 集合,必须通过 Sudents 集合来访问.
using (MyContext ctx = new MyContext()) { foreach (Homework hw in ctx.Homeworks) { Console.WriteLine($ " {hw.Description} " ); } }
上述代码会抛异常.
这很明了,就是说你必须通过 Student 实体才能访问 Homework。所以,正确的做法要这样:
using (MyContext ctx = new MyContext()) { ctx.Subjects.Load(); // 这个可不会自动加载,必须Load foreach (Student stu in ctx.Students) { Console.WriteLine( " 【{0}】同学 " , stu.Name); foreach (Homework work in stu.Homeworks ) { Console.WriteLine( " {0}:{1} " , work.Subject? .Name, work.Description); } } }
Subjects 集合为什么要显式地调用 Load 方法呢?因为 Homework 与 Subject 实体并没有建立主从关系,Subject 对象要手动加载.
这样访问就不出错了.
----------------------------------------------------------------------------------- 。
最后,咱们来总结一下:
1、普通关系的数据未自动加载,要显式Load,或者 Include 方法加载。主从关系会自动加载从属数据; 。
2、建立主从关系后,主实体对从实体是完全控制了,不仅自动生成级联删除等约束,而且你还不能直接访问从实体,只能透过主实体访问;普通关系的实体需要手动配置约束.
。
======================================================== 。
下面是老周讲故事时间.
上大学的时候,在《程序员》杂志上看过一句很“权威”的话:程序员是世上最有尊严的职业,不用酒局饭局,不用看人脸色,想干啥干啥,自由得很。然而,“多年以后一场大雨惊醒沉睡的我,突然之间都市的霓虹都不再闪烁”。客户说需求要这样这样,你改不改?改完之后客户又说还是改回那样那样,你改不改?总奸,哦不,总监说要这样这样,你能那样那样吗?客户说:“我们希望增加XXX功能,最好可以分开YYY、KKK 来管理。这些对你们来很简单的,动动鼠标就好了嘛!” 你动动鼠标试试?
再说了,哪个公司哪个单位的领导不是酒囊饭袋?IT 公司没有吗?哪儿都有,这世界最不缺的就是酒囊饭袋,最缺的是成吉思汗.
所以说,最TM自由、耍得最爽的就写博客,爱写啥写啥,套用土杰伦的歌词就是“你爱看就看,不爱看拉倒”。至于码农,就如同被压迫数千年的农民一样,没本质区别。所以,我们在给后辈讲码农生涯时,千万不要给他们画大饼,充不了饥。我们更应该教会他们程序员的最基本职业道德—— sudo rm -rf /*.
。
最后此篇关于【EFCore】主从实体关系与常见实体关系的区别的文章就讲到这里了,如果你想了解更多关于【EFCore】主从实体关系与常见实体关系的区别的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
平时很少在jquery中用到this。查看代码时发现用到了,就调试出this的值,心想原来如此。还是挺有用的。这里总结一下this与$(this)的区别和使用。 $(this)生成的是什么?
使用单例类和应用程序范围的托管 bean 来保存应用程序数据有区别吗? 我需要查找某些 JNDI 资源,例如远程 bean 接口(interface),因此我为自己编写了一个单例来缓存我的引用并且只允
如果您仔细查看包含的图片,您会注意到您可以使用 Eclipse IDE 重构 Groovy 代码并将方法转换为闭包,反之亦然。那么,闭包到底是什么,它与方法有什么不同呢?有人可以举一个使用闭包的好例子
vagrant box repackage有什么区别( docs ) 和 vagrant package ( docs )? 我意识到 vagrant package仅适用于 VirtualBox 提
我想看看是否有人可以解释为什么以下代码适用于 valueOf 但不适用于其他代码。 import java.math.BigDecimal; public class Change { publ
这个问题已经有答案了: 已关闭12 年前。 Possible Duplicates: What is Closures/Lambda in PHP or Javascript in layman te
This question already has answers here: Vagrant, Docker, Puppet, Chef (3个答案) 2年前关闭。 docker和chef有什么共同
以下代码在95%的机器上产生相同的输出,但是在几台机器上却有所不同。在 Debug模式下,输出: Changing from New to Fin OK 但在 Release模式下: Changing
////Creating Object var Obj; // init Object Obj= {}; 它们之间有什么区别两个? 有没有可能把它变成一个单行? 这样使用有什么好处吗?
我想找出定时器服务之间的区别。我应该使用哪个以及何时使用。我正在使用 Jboss 应用服务器。 1) java.ejb.Schedule。 @Schedule注解或配置自xml。 2) javax.e
我发现在 C++ 中可以通过三种不同的方式将对象传递给函数。假设我的类(class)是这样的: class Test { int i; public: Test(int x);
有什么区别。 public class Test { public static void main(String args[]) { String toBeCast = "c
如果我有一列,设置为主索引,设置为INT。 如果我不将其设置为自动递增,而只是将唯一的随机整数插入其中,与自动递增相比,这是否会减慢 future 的查询速度? 如果我在主索引和唯一索引为 INT 的
这两种日期格式有什么区别。第一个给出实际时间,第二个给出时间购买添加时区偏移值。 NSDateFormatter * dateFormatter = [[NSDateFormatter alloc]
如果有一个函数,请说foo: function foo() { console.log('bar'); } 那么在 JavaScript 中,从另一个函数调用一个函数有什么区别,如下所示: f
关闭。这个问题是opinion-based 。目前不接受答案。 想要改进这个问题吗?更新问题,以便 editing this post 可以用事实和引文来回答它。 . 已关闭 4 年前。 Improv
代码是什么: class Time { private: int hours; int minutes; int seconds; pu
我知道这是非常基本的,但有人介意解释一下这两个数组声明之间的区别吗: #include array myints; ...和: int myints[5]; ...以及为什么 myints.size
我学会了如何根据 http://reference.sitepoint.com/css/specificity 计算 css 特异性但是,基于this reference,我不明白伪类(来自c)和伪元
为什么在运行 2) 时会出现额外的空行?对我来说 1 就像 2。那么为什么 2) 中的额外行? 1) export p1=$(cd $(dirname $0) && pwd) #
我是一名优秀的程序员,十分优秀!