找回密码
 立即注册
首页 业界区 业界 【EF Core】再谈普通实体关系与 Owned 关系的区别 ...

【EF Core】再谈普通实体关系与 Owned 关系的区别

宛蛲 3 小时前
在很多个世纪前,老周曾写过实体之间普通关系(一对一,一对多,多对多)与 Owned 关系的区别。不过,那次写得比较粗浅,逼格不够高,于是,老周厚着脸皮地决定重新写一下。
首先,为什么这次老周用原单词 Owned 呢,官方文档目前的翻译(怀疑是机器干的)为“从属”,这种说法与普通关系数据库中一对多、多对多等关系描述不太 好区分。其实老周觉得应该把 Owned 翻译为“独占”关系——你完全属于我的。普通关系中的厕所是公共厕所,我可以用,邻居A、B、C也可以用;而 Owned 关系中的厕所是私人的,我用我家的厕所,A用A家自己的厕所,B不能用A家的厕所。
这种玩意儿比某少年马戏团的粉丝还抽象,要理解最好的方法是比较。本文老周就对这两类关系做一轮大比拼。
One and One

首先我们来看“一”和“一”的方式。为了保持数据结构的一致,咱们用这三个实体来实验。
  1. public class HardwareInfo
  2. {
  3.     public int HwID { get; set; }               // 主键
  4.     public long MemorySize { get; set; }        // 内存大小
  5.     public int HarddiskNum { get; set; }        // 硬盘数量
  6.     public long HDDSize { get; set; }           // 硬盘大小
  7.     public bool InteGrp { get; set; }           // 是否有集显
  8. }
  9. public class Desktop
  10. {
  11.     public int ID { get; set; }                 // 主键
  12.     public HardwareInfo HWInfo { get; set; }    // 硬件信息
  13. }
  14. public class Laptop
  15. {
  16.     public int ID { get; set; }                  // 主键
  17.     public HardwareInfo HWInfo { get; set; }     // 硬件信息
  18. }
复制代码
HardwareInfo 表示硬件参数,不管是台式机(Desktop)还是笔记本(Laptop)都可以共用这样的数据结构。
先定义用在普通关系的上下文类——MyContextR,R结尾表示 Relational。
  1. public class MyContextR : DbContext
  2. {
  3.     public DbSet<Desktop> PCs { get; set; }
  4.     public DbSet<Laptop> Laps { get; set; }
  5.     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  6.     {
  7.         optionsBuilder.UseSqlServer(@"server=<你的服务器>;database=rdb")
  8.         <strong>.LogTo(m </strong><strong>=></strong><strong> Debug.WriteLine(m))</strong>;
  9.     }
  10.     protected override void OnModelCreating(ModelBuilder mb)
  11.     {
  12.         // 配置主键
  13.         mb.Entity<HardwareInfo>().HasKey(m => m.HwID);
  14.         mb.Entity<Laptop>(ent =>
  15.         {
  16.             ent.HasKey(k => k.ID);
  17.             ent.HasOne(x => x.HWInfo);
  18.         });
  19.         mb.Entity<Desktop>(eb =>
  20.         {
  21.             eb.HasKey(a => a.ID);
  22.             eb.HasOne(y => y.HWInfo);
  23.         });
  24.     }
  25. }
复制代码
由于老周在定义实体类时“粗心大意”,主键属性的命名无法让 EF Core 自动识别,所以要在 OnModelCreating 方法中显式配置一下。注意,HasOne 让它们建立一对一的关系,即PC有一个HardwareInfo 实例,笔记本也有。
第二个上下文类是面向“独占”关系的 MyContextO,O 结尾表示 Owned。
  1. public class MyContextO : DbContext
  2. {
  3.     public DbSet<Laptop> Laps { get; set; }
  4.     public DbSet<Desktop> PCs { get; set; }
  5.     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  6.     {
  7.         optionsBuilder.UseSqlServer(@"server=<你的服务器>;database=odb")
  8.         .LogTo(g => Debug.WriteLine(g));
  9.     }
  10.     protected override void OnModelCreating(ModelBuilder mb)
  11.     {
  12.         mb.Entity<Laptop>().HasKey(m => m.ID);
  13.         mb.Entity<Desktop>().HasKey(n => n.ID);
  14.         mb.Entity<Laptop>().OwnsOne(x => x.HWInfo);
  15.         mb.Entity<Desktop>().OwnsOne(w => w.HWInfo);
  16.     }
  17. }
复制代码
OwnsOne 表示一占一,PC占用一个HardwareInfo实例,笔记本也占用一个,两者不相干。这种情形 HardwareInfo 是不需要主键的,为什么?往下看你就懂了。
咱们依次实例化这两个上下文对象,然后让它自己创建数据库。
  1. static void Main(string[] args)
  2. {
  3.     using MyContextR c1 = new();
  4.     c1.Database.EnsureCreated();
  5.     using MyContextO c2 = new();
  6.     c2.Database.EnsureCreated();
  7. }
复制代码
实验结果发现,普通一对一关系中,创建了三个表:
  1. CREATE TABLE [HardwareInfo] (
  2.     [HwID] int NOT NULL IDENTITY,
  3.     [MemorySize] bigint NOT NULL,
  4.     [HarddiskNum] int NOT NULL,
  5.     [HDDSize] bigint NOT NULL,
  6.     [InteGrp] bit NOT NULL,
  7.     CONSTRAINT [PK_HardwareInfo] PRIMARY KEY ([HwID]);
  8. CREATE TABLE [Laps] (
  9.     [ID] int NOT NULL IDENTITY,
  10.     [HWInfoHwID] int NULL,
  11.     CONSTRAINT [PK_Laps] PRIMARY KEY ([ID]),
  12.     CONSTRAINT [FK_Laps_HardwareInfo_HWInfoHwID] FOREIGN KEY ([HWInfoHwID]) REFERENCES [HardwareInfo] ([HwID])
  13. );
  14. CREATE TABLE [PCs] (
  15.     [ID] int NOT NULL IDENTITY,
  16.     [HWInfoHwID] int NULL,
  17.     CONSTRAINT [PK_PCs] PRIMARY KEY ([ID]),
  18.     CONSTRAINT [FK_PCs_HardwareInfo_HWInfoHwID] FOREIGN KEY ([HWInfoHwID]) REFERENCES [HardwareInfo] ([HwID])
  19. );
复制代码
EF Core 这货还挺聪明的,把外键分别放在 Desktop 和 Laptop 中,这样可避免在 HardwareInfo 中出现两个外键,不好约束。毕竟这是一对一关系,外键放在哪一端都可以。
然后看看“独占”关系中的一对一,它创建了两个表:
  1. CREATE TABLE [Laps] (
  2.     [ID] int NOT NULL IDENTITY,
  3.     [HWInfo_HwID] int NULL,
  4.     [HWInfo_MemorySize] bigint NULL,
  5.     [HWInfo_HarddiskNum] int NULL,
  6.     [HWInfo_HDDSize] bigint NULL,
  7.     [HWInfo_InteGrp] bit NULL,
  8.     CONSTRAINT [PK_Laps] PRIMARY KEY ([ID])
  9. );
  10. CREATE TABLE [PCs] (
  11.     [ID] int NOT NULL IDENTITY,
  12.     [HWInfo_HwID] int NULL,
  13.     [HWInfo_MemorySize] bigint NULL,
  14.     [HWInfo_HarddiskNum] int NULL,
  15.     [HWInfo_HDDSize] bigint NULL,
  16.     [HWInfo_InteGrp] bit NULL,
  17.     CONSTRAINT [PK_PCs] PRIMARY KEY ([ID])
  18. );
复制代码
你没看错,只有两个表,HardwareInfo 直接被拆开了,Desktop和Laptop各拥有一份。现在你明白了吧,为什么 HardwareInfo 在这种关系下不需要主键,因为它们不独成表。
那么,如果让 HardwareInfo 独立建表呢,又会怎样?咱们把 MyContextO 类的代码改一下,为 HardwareInfo 类单独建表。
  1. public class MyContextO : DbContext
  2. {
  3.     public DbSet<Laptop> Laps { get; set; }
  4.     public DbSet<Desktop> PCs { get; set; }
  5.     ……
  6.     protected override void OnModelCreating(ModelBuilder mb)
  7.     {
  8.         mb.Entity<Desktop>(et =>
  9.         {
  10.             et.HasKey(a => a.ID);
  11.             et.OwnsOne(b => b.HWInfo, ob =>
  12.             {
  13.                 ob.ToTable("Desktop_HW");
  14.                 ob.WithOwner();
  15.             });
  16.         });
  17.         mb.Entity<Laptop>(et =>
  18.         {
  19.             et.HasKey(a => a.ID);
  20.             et.OwnsOne(m => m.HWInfo, ob =>
  21.             {
  22.                 ob.ToTable("Laptop_HW");
  23.                 ob.WithOwner();
  24.             });
  25.         });
  26.     }
  27. }
复制代码
这个地方,WithOwner 方法可以不调用,因为 HardwareInfo 类没有定义指向 Laptop 或 Desktop 的反向导航属性。
这一次,会创建四个表:
  1. CREATE TABLE [Desktop_HW] (
  2.     [DesktopID] int NOT NULL,
  3.     [HwID] int NOT NULL,
  4.     [MemorySize] bigint NOT NULL,
  5.     [HarddiskNum] int NOT NULL,
  6.     [HDDSize] bigint NOT NULL,
  7.     [InteGrp] bit NOT NULL,
  8.     CONSTRAINT [PK_Desktop_HW] PRIMARY KEY ([DesktopID]),
  9.     CONSTRAINT [FK_Desktop_HW_PCs_DesktopID] FOREIGN KEY ([DesktopID]) REFERENCES [PCs] ([ID]) ON DELETE CASCADE
  10. );
  11. CREATE TABLE [Laptop_HW] (
  12.     [LaptopID] int NOT NULL,
  13.     [HwID] int NOT NULL,
  14.     [MemorySize] bigint NOT NULL,
  15.     [HarddiskNum] int NOT NULL,
  16.     [HDDSize] bigint NOT NULL,
  17.     [InteGrp] bit NOT NULL,
  18.     CONSTRAINT [PK_Laptop_HW] PRIMARY KEY ([LaptopID]),
  19.     CONSTRAINT [FK_Laptop_HW_Laps_LaptopID] FOREIGN KEY ([LaptopID]) REFERENCES [Laps] ([ID]) ON DELETE CASCADE
  20. );
  21. CREATE TABLE [PCs] (
  22.     [ID] int NOT NULL IDENTITY,
  23.     CONSTRAINT [PK_PCs] PRIMARY KEY ([ID])
  24. );
  25. CREATE TABLE [Laps] (
  26.     [ID] int NOT NULL IDENTITY,
  27.     CONSTRAINT [PK_Laps] PRIMARY KEY ([ID])
  28. );
复制代码
EF Core 很有才,咱们没有为 HardwareInfo 定义主键,于是它自己生成了,在 Laptop_HW 表中生成 LaptopID 列作为主键,同时也作为外键,引用 Laptop.ID;在 Desktop_HW 表中生成了 DesktopID 列作为主键,同时作为外键,引用 Desktop.ID。
还要补充解释一下模型配置代码。
  1. mb.Entity<Laptop>(et =>
  2. {
  3.      et.HasKey(a => a.ID);
  4.      et.OwnsOne(m => m.HWInfo, ob =>
  5.      {
  6.          ob.ToTable("Laptop_HW");
  7.          //ob.WithOwner();
  8.      });
  9. });
复制代码
ToTable 的调用在此处是必须的,否则按默认约定,它会使用表名 Laps,即和 Laptop 保持一致,这会导致出错。而且,Laptop 和 Desktop 不能共享一个 HardwareInfo 实体。这样配置也会报错:
  1. protected override void OnModelCreating(ModelBuilder mb)
  2. {
  3.     mb.Entity<Desktop>(et =>
  4.     {
  5.         et.HasKey(a => a.ID);
  6.         et.OwnsOne(b => b.HWInfo, ob =>
  7.         {
  8.             ob.ToTable("HW_info");
  9.         });
  10.     });
  11.     mb.Entity<Laptop>(et =>
  12.     {
  13.         et.HasKey(a => a.ID);
  14.         et.OwnsOne(m => m.HWInfo, ob =>
  15.         {
  16.             ob.ToTable("HW_info");
  17.         });
  18.     });
  19. }
复制代码
这就等于 Desktop 和 Laptop 同时占有相同的 HardwareInfo 实例,运行时也会报错。
 
One and Many

 这里咱们已经没有必要再与普通的一对多关系对比了,上面的对比已经明确 Owned 关系是独占性的,不共享实例。下面咱们看看实体独占多个实例的情况。这种情况下,被占有的对象不会与主对象共用一个表了——拆分的列无法表示多个实例。
举个例子。
  1. public class AddressInfo
  2. {
  3.     /// <summary>
  4.     /// 这里有主键
  5.     /// </summary>
  6.     public int AddrID {  get; set; }
  7.     /// <summary>
  8.     /// 省
  9.     /// </summary>
  10.     public string Province { get; set; } = "";
  11.     /// <summary>
  12.     /// 市
  13.     /// </summary>
  14.     public string City { get; set; } = "";
  15.     /// <summary>
  16.     /// 镇
  17.     /// </summary>
  18.     public string Town { get; set; } = "";
  19.     /// <summary>
  20.     /// 路
  21.     /// </summary>
  22.     public string Road { get; set; } = "";
  23.     /// <summary>
  24.     /// 街道
  25.     /// </summary>
  26.     public string Street { get; set; } = "";
  27.     /// <summary>
  28.     /// 邮编
  29.     /// </summary>
  30.     public string? ZipCode { get; set; }
  31. }
  32. public class Student
  33. {
  34.     public int StudentID { get; set; }
  35.     public IList? Addresses { get; set; }
  36. }
复制代码
如果这里的地址表示收货地址,于是每个学生都可以拥有多个地址。
然后,上下文类是这样的。
  1. public class MyContext : DbContext
  2. {
  3.     public DbSet<Student> Students { get; set; }
  4.     protected override void OnConfiguring(DbContextOptionsBuilder ob)
  5.     {
  6.         SqlConnectionStringBuilder strbd = new();
  7.         strbd.DataSource = <你的服务器>;
  8.         strbd.InitialCatalog = "TestDB";
  9.         ob.UseSqlServer(strbd.ConnectionString)
  10.             .LogTo(x => Console.WriteLine(x));
  11.     }
  12.     protected override void OnModelCreating(ModelBuilder modelBuilder)
  13.     {
  14.         modelBuilder.Entity<Student>(ste =>
  15.         {
  16.             ste.HasKey(x => x.StudentID).HasName("PK_Stu_id");
  17.             // 它占有多个 Addr
  18.             ste.OwnsMany(k => k.Addresses, ob =>
  19.             {
  20.                 // 此处可以配置主键
  21.                 ob.HasKey(x => x.AddrID);
  22.                 ob.WithOwner()
  23.                     .HasForeignKey("stu_id").HasConstraintName("FK_StuID");
  24.             });
  25.         });
  26.     }
  27. }
复制代码
数据库会创建两张表:
  1. CREATE TABLE [Students] (
  2.           [StudentID] int NOT NULL IDENTITY,
  3.           CONSTRAINT [PK_Stu_id] PRIMARY KEY ([StudentID])
  4.       );
  5. CREATE TABLE [AddressInfo] (
  6.           [AddrID] int NOT NULL IDENTITY,
  7.           [Province] nvarchar(max) NOT NULL,
  8.           [City] nvarchar(max) NOT NULL,
  9.           [Town] nvarchar(max) NOT NULL,
  10.           [Road] nvarchar(max) NOT NULL,
  11.           [Street] nvarchar(max) NOT NULL,
  12.           [ZipCode] nvarchar(max) NULL,
  13.           [stu_id] int NOT NULL,
  14.           CONSTRAINT [PK_AddressInfo] PRIMARY KEY ([AddrID]),
  15.           CONSTRAINT [FK_StuID] FOREIGN KEY <strong>([stu_id]) REFERENCES [Students] ([StudentID])</strong> ON DELETE CASCADE
  16.       );
复制代码
AddressInfo 表会创建一个外键来引用 Students 表的主键列。
接着,咱们加一个 Teacher 实体,和学生一样,老师也有多个收货地址。
  1. public class Teacher
  2. {
  3.     public int Tid { get; set; }
  4.     public IList? Addresses { get; set; }
  5. }
复制代码
上下文类也要做相应修改。
  1. public class MyContext : DbContext
  2. {
  3.     public DbSet<Student> Students { get; set; }
  4.     public DbSet<Teacher> Teachers { get; set; }
  5.    ……
  6.     protected override void OnModelCreating(ModelBuilder modelBuilder)
  7.     {
  8.         modelBuilder.Entity<Student>(ste =>
  9.         {
  10.             ste.HasKey(x => x.StudentID).HasName("PK_Stu_id");
  11.             // 它占有多个 Addr
  12.             ste.OwnsMany(k => k.Addresses, ob =>
  13.             {
  14.                 // 此处可以配置主键
  15.                 ob.HasKey(x => x.AddrID);
  16.                 // 必须要表名
  17.                 ob.ToTable("Stu_Addr");
  18.                 ob.WithOwner()
  19.                     .HasForeignKey("stu_id").HasConstraintName("FK_StuID");
  20.             });
  21.         });
  22.         modelBuilder.Entity<Teacher>(tet =>
  23.         {
  24.             tet.HasKey(t => t.Tid).HasName("PK_TeacherID");
  25.             // 占用多个地址
  26.             tet.OwnsMany(t => t.Addresses, ob =>
  27.             {
  28.                 ob.HasKey(o => o.AddrID);   // 主键
  29.                 ob.ToTable("Teacher_Addr"); // 表名
  30.                 ob.WithOwner().HasForeignKey("teach_id").HasConstraintName("FK_TeachID");
  31.             });
  32.         });
  33.     }
  34. }
复制代码
这种情况下必须配置 AddressInfo 的表名。
这样数据库会创建四张表:
  1. CREATE TABLE [Students] (
  2.           [StudentID] int NOT NULL IDENTITY,
  3.           CONSTRAINT [PK_Stu_id] PRIMARY KEY ([StudentID])
  4.       );
  5. CREATE TABLE [Teachers] (
  6.           [Tid] int NOT NULL IDENTITY,
  7.           CONSTRAINT [PK_TeacherID] PRIMARY KEY ([Tid])
  8.       );
  9. CREATE TABLE [Stu_Addr] (
  10.           [AddrID] int NOT NULL IDENTITY,
  11.           [Province] nvarchar(max) NOT NULL,
  12.           [City] nvarchar(max) NOT NULL,
  13.           [Town] nvarchar(max) NOT NULL,
  14.           [Road] nvarchar(max) NOT NULL,
  15.           [Street] nvarchar(max) NOT NULL,
  16.           [ZipCode] nvarchar(max) NULL,
  17.           [stu_id] int NOT NULL,
  18.           CONSTRAINT [PK_Stu_Addr] PRIMARY KEY ([AddrID]),
  19.           CONSTRAINT [FK_StuID] FOREIGN KEY ([stu_id]) REFERENCES [Students] ([StudentID]) ON DELETE CASCADE
  20.       );
  21. CREATE TABLE [Teacher_Addr] (
  22.           [AddrID] int NOT NULL IDENTITY,
  23.           [Province] nvarchar(max) NOT NULL,
  24.           [City] nvarchar(max) NOT NULL,
  25.           [Town] nvarchar(max) NOT NULL,
  26.           [Road] nvarchar(max) NOT NULL,
  27.           [Street] nvarchar(max) NOT NULL,
  28.           [ZipCode] nvarchar(max) NULL,
  29.           [teach_id] int NOT NULL,
  30.           CONSTRAINT [PK_Teacher_Addr] PRIMARY KEY ([AddrID]),
  31.           CONSTRAINT [FK_TeachID] FOREIGN KEY ([teach_id]) REFERENCES [Teachers] ([Tid]) ON DELETE CASCADE
  32.       );
复制代码
 
最后,咱们验证一下,Owned 关系是否真的不能共享实例。
  1. using(MyContext c = new())
  2. {
  3.     // 四个地址
  4.     AddressInfo addr1 = new()
  5.     {
  6.         Province = "冬瓜省",
  7.         City = "嘎子市",
  8.         Town = "小连子镇",
  9.         Road = "牛逼路",
  10.         Street = "春风街3999号",
  11.         ZipCode = "62347"
  12.     };
  13.     AddressInfo addr2 = new()
  14.     {
  15.         Province = "提头省",
  16.         City = "抬扛台",
  17.         Town = "烟斗镇",
  18.         Road = "王八路",
  19.         Street = "送人头街666号",
  20.         ZipCode = "833433"
  21.     };
  22.     // 教师实例
  23.     Teacher tt = new();
  24.     // 学生实例
  25.     Student ss = new();
  26.     // 让他们使用相同的地址实例
  27.     tt.Addresses = new List( [addr1, addr2] );
  28.     ss.Addresses = new List( [addr1, addr2] );
  29.     // 添加实体
  30.     c.Students.Add(ss);
  31.     c.Teachers.Add(tt);
  32.     // 保存到数据库
  33.     c.SaveChanges();
  34. }
复制代码
运行后,未抛出异常,但有警告。而且数据库中也有数据。
下面咱们改一下某个地址的 City 属性。
  1. using(MyContext c2 = new())
  2. {
  3.     var r1 = c2.Students.ToArray();
  4.     var r2 = c2.Teachers.ToArray();
  5.     AddressInfo? addr = r1.First()?.Addresses?.FirstOrDefault();
  6.     if(addr != null)
  7.     {
  8.         <strong>addr.City </strong><strong>= "烤鸭市"</strong><strong>;</strong>
  9.     }
  10.     c2.SaveChanges();
  11. }
复制代码
运行一下。
然后咱们查询一下两个地址表的数据。
  1. select * from Stu_Addr;
  2. select * from Teacher_Addr;
复制代码
1.png

只有 ID = 1 的学生的第一个地址的 City 属性被更新,而教师地址未更新。可见,两个实体是不共响地址实例的。这很好理解嘛,毕竟是两个表的。
 
那么,如果把 Student - AddressInfo,Teacher - AddressInfo 的关系改为普通的一对多关系,又会怎样?
  1. public class MyContext : DbContext
  2. {
  3.     public DbSet<Student> Students { get; set; }
  4.     public DbSet<Teacher> Teachers { get; set; }
  5.     protected override void OnConfiguring(DbContextOptionsBuilder ob)
  6.     {
  7.         ……
  8.     }
  9.     protected override void OnModelCreating(ModelBuilder modelBuilder)
  10.     {
  11.         modelBuilder.Entity<Student>(ste =>
  12.         {
  13.             ste.HasKey(x => x.StudentID).HasName("PK_Stu_id");
  14.             
  15.             ste.HasMany(x => x.Addresses)
  16.                 .WithOne()
  17.                 .HasForeignKey("stu_id")
  18.                 .HasConstraintName("FK_StuID");
  19.         });
  20.         modelBuilder.Entity<Teacher>(tet =>
  21.         {
  22.             tet.HasKey(t => t.Tid).HasName("PK_TeacherID");
  23.             
  24.             tet.HasMany(f => f.Addresses)
  25.                 .WithOne()
  26.                 .HasForeignKey("teacher_id")
  27.                 .HasConstraintName("FK_TeacherID");
  28.         });
  29.         // 注意:这时候 AddressInfo 实体需要主键
  30.         modelBuilder.Entity().<strong>HasKey(x =></strong><strong> x.AddrID)</strong>;
  31.     }
  32. }
复制代码
改为普通一对多关系时要注意,Student、Teacher、AddressInfo 三个实体都需要主键的, Owned 实体、复合类型(老周以前介绍过)这些不需要主键。
删除刚刚的数据库,重新建立新的数据库,然后写入数据。
  1. using(MyContext c = new())
  2. {
  3.     c.Database.EnsureDeleted();
  4.     c.Database.EnsureCreated();
  5.     // 两个地址
  6.     AddressInfo addr1 = new()
  7.     {
  8.         Province = "冬瓜省",
  9.         City = "嘎子市",
  10.         Town = "小连子镇",
  11.         Road = "牛逼路",
  12.         Street = "春风街3999号",
  13.         ZipCode = "62347"
  14.     };
  15.     AddressInfo addr2 = new()
  16.     {
  17.         Province = "提头省",
  18.         City = "抬扛台",
  19.         Town = "烟斗镇",
  20.         Road = "王八路",
  21.         Street = "送人头街666号",
  22.         ZipCode = "833433"
  23.     };
  24.     // 教师实例
  25.     Teacher tt = new();
  26.     // 学生实例
  27.     Student ss = new();
  28.     // 让他们使用相同的地址实例
  29.     tt.Addresses = new List( [addr1, addr2] );
  30.     ss.Addresses = new List( [addr1, addr2] );
  31.     // 添加实体
  32.     c.Students.Add(ss);
  33.     c.Teachers.Add(tt);
  34.     // 保存到数据库
  35.     c.SaveChanges();
  36. }
复制代码
这时候,地址表只有一个,插入的数据如下:
2.png

教师和学生共享一个地址表,分别通过 stu_id 和 teacher_id 外键引用主表记录。
然后更改第一个地址的 City 属性。
  1. using(MyContext c2 = new())
  2. {
  3.      var r1 = c2.Students.Include(s => s.Addresses).ToArray();
  4.      var r2 = c2.Teachers.Include(t => t.Addresses).ToArray();
  5.      AddressInfo? addr = r1.First()?.Addresses?.FirstOrDefault();
  6.      if(addr != null)
  7.      {
  8.          addr.City = "烤鸭市";
  9.      }
  10.      c2.SaveChanges();
  11. }
复制代码
地址表的数据变为:
3.png

由于教师和学生共用一个地址表,所以他们的地址信息会相同。
  1. using(MyContext c3 = new())
  2. {
  3.      // 加载全部数据
  4.      var students = c3.Students.Include(x => x.Addresses);
  5.      var teachers = c3.Teachers.Include(x => x.Addresses);
  6.      Console.WriteLine("---------- 学生 ---------");
  7.      foreach(var s in students)
  8.      {
  9.          Console.WriteLine($"学生:{s.StudentID}");
  10.          if(s.Addresses != null)
  11.          {
  12.              foreach(var a in s.Addresses)
  13.              {
  14.                  Console.WriteLine($"\t{a.AddrID}, {a.Province}, {a.City}, {a.Town}");
  15.              }
  16.          }
  17.      }
  18.      
  19.      Console.WriteLine("\n---------- 教师 ---------");
  20.      foreach (var t in teachers)
  21.      {
  22.          Console.WriteLine($"老师:{t.Tid}");
  23.          if (t.Addresses != null)
  24.          {
  25.              foreach (var a in t.Addresses)
  26.              {
  27.                  Console.WriteLine($"\t{a.AddrID}, {a.Province}, {a.City}, {a.Town}");
  28.              }
  29.          }
  30.      }
  31. }
复制代码
4.png

 
【总结】
1、Owned 关系中,主实体完全掌控从实体,并且不与其他实体共享数据;
2、被“独占”的实体不用使用 ModelBuilder.Entity 方法配置,因此在 DbContext 派生时,也不能声明为 DbSet 属性。而普通关系中的实体是允许的;
3、Owned 关系有一 Own 一、一 Own 多,不存在 多 Own 多。多 Own 多 就违背“独占”原则了。普通关系中可以有多对多;
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册