【EF Core】主从实体关系与常见实体关系的区别

这篇具有很好参考价值的文章主要介绍了【EF Core】主从实体关系与常见实体关系的区别。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

上次老周扯了有关主、从实体的话题,本篇咱们再挖一下,主、从实体之间建立的关系,跟咱们常用的一对一、一对多这些关系之间有什么不同。

先看看咱们从学习数据库开始就特熟悉的常用关系——多对多、一对一、一对多说起。数据实体之间会建立什么样的关系,并不是规则性的,而是要看数据的功能。比如你家养的狗狗和水果(你家狗狗可能不吃水果,但老周养的动物基本是什么都吃的,因为从它们幼年起,老周就训练它们,对食物要来者不拒,就算哪天它们不想跟着老周混,出去流浪也不会饿死,适应性更强)。

假设:

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();

【EF Core】主从实体关系与常见实体关系的区别

产生的数据表如下图所示:

【EF Core】主从实体关系与常见实体关系的区别

 文章来源地址https://www.toymoban.com/news/detail-564577.html

我们上面的这个模型还是有点问题的,可以看一下,生成的数据表是没有删除约束的。

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();
    }
}

但删除的时候会遇到错误。

【EF Core】主从实体关系与常见实体关系的区别

这表明咱们要配置级联删除。

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);
    }
}

现在再删一次看看。

【EF Core】主从实体关系与常见实体关系的区别

可以看到,与第一位学生有关的作业记录也一并被删除了。生成的数据表也与前面有一点差异。

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}");
    }
}

上述代码会抛异常。

【EF Core】主从实体关系与常见实体关系的区别

这很明了,就是说你必须通过 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 对象要手动加载。

这样访问就不出错了。

【EF Core】主从实体关系与常见实体关系的区别

-----------------------------------------------------------------------------------

最后,咱们来总结一下:

1、普通关系的数据未自动加载,要显式Load,或者 Include 方法加载。主从关系会自动加载从属数据;

2、建立主从关系后,主实体对从实体是完全控制了,不仅自动生成级联删除等约束,而且你还不能直接访问从实体,只能透过主实体访问;普通关系的实体需要手动配置约束。

 

========================================================

下面是老周讲故事时间。

上大学的时候,在《程序员》杂志上看过一句很“权威”的话:程序员是世上最有尊严的职业,不用酒局饭局,不用看人脸色,想干啥干啥,自由得很。然而,“多年以后一场大雨惊醒沉睡的我,突然之间都市的霓虹都不再闪烁”。客户说需求要这样这样,你改不改?改完之后客户又说还是改回那样那样,你改不改?总奸,哦不,总监说要这样这样,你能那样那样吗?客户说:“我们希望增加XXX功能,最好可以分开YYY、KKK 来管理。这些对你们来很简单的,动动鼠标就好了嘛!” 你动动鼠标试试?

再说了,哪个公司哪个单位的领导不是酒囊饭袋?IT 公司没有吗?哪儿都有,这世界最不缺的就是酒囊饭袋,最缺的是成吉思汗。

所以说,最TM自由、耍得最爽的就写博客,爱写啥写啥,套用土杰伦的歌词就是“你爱看就看,不爱看拉倒”。至于码农,就如同被压迫数千年的农民一样,没本质区别。所以,我们在给后辈讲码农生涯时,千万不要给他们画大饼,充不了饥。我们更应该教会他们程序员的最基本职业道德—— sudo rm -rf /*。

 

到了这里,关于【EF Core】主从实体关系与常见实体关系的区别的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • .NET6.0 EF Core 之 DB First生成实体类

    EF Core可以使用DB First模式生成实体类具体步骤如下: 因为.NET Core中默认不包含EF Core的工具和程序包,需要通过NuGet管理器安装对应的工具和程序包,这里使用SQL Server数据库。 Microsoft.EntityFrameworkCore.SqlServer:SQL Server数据库EF提供程序 Microsoft.EntityFrameworkCore.Design:设计时使用到

    2024年02月06日
    浏览(41)
  • .NET6 + EF Core + MySQL 创建实体和数据库、EFCore 数据迁移

    接上期文章《.NET6项目连接数据库方式方法》,有人问了我几个问题,现在就这几个问题,拓展延申一下创建实体类、数据库。把ORM框架和数据迁移都写进去。 我的项目是在Linux上创建的,使用的是vscode开发工具远程开发。为了方便大家阅读和操作,我将项目down到我的本地电

    2024年02月05日
    浏览(38)
  • 记录一次EF实体跟踪错误

    在我写文章编辑接口的,出现了一个实体跟踪的错误,详情如下 System.InvalidOperationException: The instance of entity type \\\'Tag\\\' cannot be tracked because another instance with the same key value for {\\\'Id\\\'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attach

    2024年02月11日
    浏览(28)
  • 使用EF6(DB First模式)无法生成对应模型实体类

    最近升级了,Visual Stidio 2022,在使用EF6时(DB First模式),无法生成对应模型的实体类,如下:  对于该问题,我去微软社区,找到了两个解决方案: 1.从 Visual Studio2022 16.x 版本回滚到 Visual Studio2022 15.x 版本即可解决问题; 2.修改EF6的实用程序.CS.ttinclude,它默认的位置在:C:Program FilesMicro

    2024年02月11日
    浏览(23)
  • EF Core入门

    EF Core是微软官方提供的ORM框架。EF Core不仅可以操作Microsoft SQL Server、MySQL、Oracle、PostgreSQL等数据库,而且可以操作Azure Cosmos DB等NoSQL数据库 前提条件:已经完整安装了Microsoft SQL Server 下面是一个实际操作EF Core的演示 这是项目最终的目录,这里需要关注的就是 .cs 文件 首先新

    2023年04月09日
    浏览(25)
  • 4.1EF Core

    EF Core是微软官方的ORM框架,ORM即对象关系映射,也就是我们可以直接操作C#中的对象就可以完成数据库的操作。 EF Core环境搭建 首先要创建C#对象,用以对应数据库中的表,该C#对象也成为实体类。 根据所用的数据库选择NuGet包,本文使用SQLite数据库,所以安装Microsoft.EntityF

    2024年02月05日
    浏览(29)
  • EF Core并发控制

    并发控制:避免多个用户同时操作资源造成的并发冲突问题。 最好的解决方案:非数据库解决方案 数据库层面的两种策略:悲观、乐观 悲观并发控制一般采用行锁 ,表锁等排他锁对资源进行锁定,确保同时只有一个使用者操作被锁定的资源。 EF Core没有封装悲观并发控制的

    2024年02月10日
    浏览(36)
  • Net Core中使用EF Core连接Mysql数据库

    Entity Framework Core的前身是微软提供并主推的ORM框架,简称EF,其底层是对ADO.NET的封装。EF支持SQLServer、MYSQL、Oracle、Sqlite等所有主流数据库。 首先是使用时的几个模式的整理及其理解: Code First:根据代码自动创建数据库表结构甚至是数据库,可以支持多库开发,代码较少冗余

    2024年01月24日
    浏览(41)
  • EF Core + MySQL 基本增删改查

    基于EF Core + MySQL的基本增删改查,示例是基于.NET6 + EF Core + MySQL 创建实体和数据库、EFCore 数据迁移项目基础上的内容增加。同时也是对基于Canal实现MySQL 8.0 数据库数据同步项目的验证。 Controllers----添加----控制器,选择api----包含读写操作的API控制器。 将上下文类注入到User

    2024年02月08日
    浏览(40)
  • 使用EF Core创建webapi接口(二)

    有错误欢迎大家给我指正 说明:netcore webapi+net6+EF Core版本,codefirst模式(代码创建数据库) 1.netcore webapi+net6+EF Core版本,dbfirst模式(代码生成数据库)见:使用EF Core创建webapi接口(一)-CSDN博客 2.netcore webapi+net6+EF Core+vue前后端联动版本,见netcore webapi+net6+EF Core+vue3前后端联动-CSD

    2024年02月21日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包