(21)ASP.NET Core EF創建模型(關係)

  • 2019 年 10 月 24 日
  • 筆記

1.關係

關係定義兩個實體之間的關係。在關係型資料庫中,這由外鍵約束表示。

2.術語定義

有許多術語用於描述關係:
●相關實體:這是包含外鍵屬性的實體。有時稱為關係的”子級”。
●主體實體:這是包含主/備用鍵屬性的實體。有時稱為關係的 “父項”。
●外鍵:依賴實體中的屬性,用於存儲與實體相關的主體鍵屬性的值。
●主體密鑰:唯一標識主體實體的屬性。這可能是主鍵或備用密鑰。
●導航屬性:在主體和/或從屬實體上定義的屬性,該屬性包含對相關實體的引用。
●集合導航屬性:一個導航屬性,其中包含對多個相關實體的引用。
●引用導航屬性:保存對單個相關實體的引用的導航屬性。
●反嚮導航屬性:討論特定導航屬性時,此術語是指關係另一端的導航屬性。
下面的程式碼列表顯示了與之間Blog的一對多關係Post
●Post是依賴實體
●Blog是主體實體
●Post.BlogId為外鍵
●Blog.BlogId是主體鍵(在這種情況下是主鍵,而不是備用鍵)
●Post.Blog是一個引用導航屬性
●Blog.Posts是集合導航屬性
●Post.Blog是的Blog.Posts反嚮導航屬性(反之亦然)

public class Blog  {      public int BlogId { get; set; }      public string Url { get; set; }      public List<Post> Posts { get; set; }  }  public class Post  {      public int PostId { get; set; }      public string Title { get; set; }      public string Content { get; set; }      public int BlogId { get; set; }      public Blog Blog { get; set; }  }

3.約定

按照約定,當發現類型上有導航屬性時,將創建關係。如果屬性指向的類型不能由當前的資料庫提供程式映射為標量類型,則該屬性視為一個導航屬性。

4.完全定義的關係

關係最常見的模式是在關係兩端定義導航屬性,在依賴實體類中定義外鍵屬性。
如果在兩個類型之間找到一對導航屬性,則這些屬性將配置為同一關係的反嚮導航屬性。
如果依賴實體包含名為<primary key property name>、<navigation property name><primary key property name>或<principal entity name><primary key property name>的屬性,則該屬性將被配置為外鍵。

public class Blog  {      public int BlogId { get; set; }   public string Url { get; set; }      //導航屬性      public List<Post> Posts { get; set; }  }  public class Post  {      public int PostId { get; set; }      public string Title { get; set; }      public string Content { get; set; }      //外鍵屬性      public int BlogId { get; set; }      //反嚮導航屬性      public Blog Blog { get; set; }  }

5.無外鍵屬性

儘管建議在依賴實體類中定義外鍵屬性,但這並不是必需的。如果未找到外鍵屬性,則會以該名稱<navigation property name><principal key property name>引入陰影外鍵屬性。

public class Blog  {      public int BlogId { get; set; }      public string Url { get; set; }      //陰影導航屬性      public List<Post> Posts { get; set; }  }  public class Post  {      public int PostId { get; set; }      public string Title { get; set; }      public string Content { get; set; }       //陰影反嚮導航屬性      public Blog Blog { get; set; }  }

6.單個導航屬性

只包含一個導航屬性(無反嚮導航,沒有外鍵屬性)就足以具有約定定義的關係。 還可以有一個導航屬性和一個外鍵屬性。

public class Blog  {      public int BlogId { get; set; }      public string Url { get; set; }      //陰影導航屬性      public List<Post> Posts { get; set; }  }  public class Post  {      public int PostId { get; set; }      public string Title { get; set; }      public string Content { get; set; }  }

7.數據注釋

可以使用兩個數據批註來配置關係[ForeignKey]和[InverseProperty]。System.ComponentModel.DataAnnotations.Schema命名空間中提供了這些項。

7.1ForeignKey

你可以使用數據批註來配置應用程式作給定關係的外鍵屬性的屬性。通常,當不按約定發現外鍵屬性時,會執行此操作。

namespace EFModeling.DataAnnotations.Relationships.ForeignKey  {      class MyContext : DbContext      {          public DbSet<Blog> Blogs { get; set; }          public DbSet<Post> Posts { get; set; }      }      #region Entities      public class Blog      {          public int BlogId { get; set; }          public string Url { get; set; }          //導航屬性          public List<Post> Posts { get; set; }      }      public class Post      {          public int PostId { get; set; }          public string Title { get; set; }          public string Content { get; set; }          //外鍵          public int BlogForeignKey { get; set; }          //設置反嚮導航外鍵          [ForeignKey("BlogForeignKey")]          public Blog Blog { get; set; }      }      #endregion  }

7.2InverseProperty

您可以使用數據批註來配置依賴項和主體實體上的導航屬性如何配對。這通常在兩個實體類型之間存在多個導航屬性對時執行。

namespace EFModeling.DataAnnotations.Relationships.InverseProperty  {      class MyContext : DbContext      {          public DbSet<Post> Posts { get; set; }          public DbSet<User> Users { get; set; }      }      #region Entities      public class Post      {          public int PostId { get; set; }          public string Title { get; set; }          public string Content { get; set; }            public int AuthorUserId { get; set; }          public User Author { get; set; }            public int ContributorUserId { get; set; }          public User Contributor { get; set; }      }      public class User      {          public string UserId { get; set; }          public string FirstName { get; set; }          public string LastName { get; set; }            [InverseProperty("Author")]          public List<Post> AuthoredPosts { get; set; }            [InverseProperty("Contributor")]          public List<Post> ContributedToPosts { get; set; }      }      #endregion  }

8.Fluent API

若要在熟知的API中配置關係,請首先標識構成關係的導航屬性。HasOne或HasMany標識要開始配置的實體類型上的導航屬性。然後,將調用鏈接到WithOne或WithMany以標識反嚮導航。HasOne/WithOne用於引用導航屬性,HasMany / WithMany用於集合導航屬性。

namespace EFModeling.FluentAPI.Relationships.NoForeignKey  {      #region Model      class MyContext : DbContext      {          public DbSet<Blog> Blogs { get; set; }          public DbSet<Post> Posts { get; set; }          protected override void OnModelCreating(ModelBuilder modelBuilder)          {              modelBuilder.Entity<Post>()                  //配置一對多關係                  .HasOne(p => p.Blog)                  .WithMany(b => b.Posts);          }      }      public class Blog      {          public int BlogId { get; set; }          public string Url { get; set; }          public List<Post> Posts { get; set; }      }      public class Post      {          public int PostId { get; set; }          public string Title { get; set; }          public string Content { get; set; }          public Blog Blog { get; set; }      }      #endregion  }

8.1單個導航屬性

如果只有一個導航屬性,則用WithOne、WithMany的無參數重載。這表示在概念上,關係的另一端有一個引用或集合,但實體類中不包含導航屬性。

namespace EFModeling.FluentAPI.Relationships.OneNavigation  {      #region Model      class MyContext : DbContext      {          public DbSet<Blog> Blogs { get; set; }          public DbSet<Post> Posts { get; set; }          protected override void OnModelCreating(ModelBuilder modelBuilder)          {              modelBuilder.Entity<Blog>()                  //配置多對一關係                  .HasMany(b => b.Posts)                  .WithOne();          }      }      public class Blog      {          public int BlogId { get; set; }          public string Url { get; set; }          //導航屬性          public List<Post> Posts { get; set; }      }      public class Post      {          public int PostId { get; set; }          public string Title { get; set; }          public string Content { get; set; }      }      #endregion  }

8.2ForeignKey

你可以使用API來配置應用程式的外鍵屬性。

namespace EFModeling.Configuring.DataAnnotations.Samples.Relationships.ForeignKey  {      #region Model      class MyContext : DbContext      {          public DbSet<Blog> Blogs { get; set; }          public DbSet<Post> Posts { get; set; }          protected override void OnModelCreating(ModelBuilder modelBuilder)          {              modelBuilder.Entity<Post>()                  //配置一對多關係                  .HasOne(p => p.Blog)                  .WithMany(b => b.Posts)                  //配置外鍵                  .HasForeignKey(p => p.BlogForeignKey);          }      }      public class Blog      {          public int BlogId { get; set; }          public string Url { get; set; }          //導航屬性          public List<Post> Posts { get; set; }      }      public class Post      {          public int PostId { get; set; }          public string Title { get; set; }          public string Content { get; set; }          //外鍵          public int BlogForeignKey { get; set; }          public Blog Blog { get; set; }      }      #endregion  }

下面的程式碼列表演示如何配置複合外鍵:

namespace EFModeling.Configuring.DataAnnotations.Samples.Relationships.CompositeForeignKey  {      #region Model      class MyContext : DbContext      {          public DbSet<Car> Cars { get; set; }          protected override void OnModelCreating(ModelBuilder modelBuilder)          {              modelBuilder.Entity<Car>()                  //配置複合主鍵                  .HasKey(c => new { c.State, c.LicensePlate });                  modelBuilder.Entity<RecordOfSale>()                  //配置一對多關係                  .HasOne(s => s.Car)                  .WithMany(c => c.SaleHistory)                  //配置外鍵                  .HasForeignKey(s => new { s.CarState, s.CarLicensePlate });          }      }      public class Car      {          public string State { get; set; }          public string LicensePlate { get; set; }          public string Make { get; set; }          public string Model { get; set; }          //導航屬性          public List<RecordOfSale> SaleHistory { get; set; }      }      public class RecordOfSale      {          public int RecordOfSaleId { get; set; }          public DateTime DateSold { get; set; }          public decimal Price { get; set; }          //State對應CarState          public string CarState { get; set; }          //LicensePlate 對應CarLicensePlate          public string CarLicensePlate { get; set; }          public Car Car { get; set; }      }      #endregion  }

您可以使用的HasForeignKey(…)字元串重載將影子屬性配置為外鍵。建議先將影子屬性顯式添加到模型,然後再將其用作外鍵:

class MyContext : DbContext  {      public DbSet<Blog> Blogs { get; set; }      public DbSet<Post> Posts { get; set; }      protected override void OnModelCreating(ModelBuilder modelBuilder)      {          // Add the shadow property to the model          modelBuilder.Entity<Post>()               //配置外鍵              .Property<int>("BlogForeignKey");          // Use the shadow property as a foreign key          modelBuilder.Entity<Post>()              //配置一對多關係              .HasOne(p => p.Blog)              .WithMany(b => b.Posts)              //配置外鍵              .HasForeignKey("BlogForeignKey");      }  }  public class Blog  {      public int BlogId { get; set; }      public string Url { get; set; }      public List<Post> Posts { get; set; }  }  public class Post  {      public int PostId { get; set; }      public string Title { get; set; }      public string Content { get; set; }      public Blog Blog { get; set; }  }

8.3無導航屬性

不一定需要提供導航屬性。你可以直接在關係的一端提供外鍵。

namespace EFModeling.FluentAPI.Relationships.NoNavigation  {      #region Model      class MyContext : DbContext      {          public DbSet<Blog> Blogs { get; set; }          public DbSet<Post> Posts { get; set; }          protected override void OnModelCreating(ModelBuilder modelBuilder)          {              modelBuilder.Entity<Post>()                  //配置一對多關係                  .HasOne<Blog>()                  .WithMany()                  //配置外鍵                  .HasForeignKey(p => p.BlogId);          }      }      public class Blog      {          public int BlogId { get; set; }          public string Url { get; set; }      }      public class Post      {          public int PostId { get; set; }          public string Title { get; set; }          public string Content { get; set; }          public int BlogId { get; set; }      }      #endregion  }

9.主體密鑰

如果你希望外鍵引用主鍵之外的屬性,則可以使用熟知的API來配置關係的主體鍵屬性。 配置為主體密鑰的屬性將自動設置為備用密鑰。

class MyContext : DbContext  {      public DbSet<Car> Cars { get; set; }      protected override void OnModelCreating(ModelBuilder modelBuilder)      {          modelBuilder.Entity<RecordOfSale>()              .HasOne(s => s.Car)              .WithMany(c => c.SaleHistory)              .HasForeignKey(s => s.CarLicensePlate)              .HasPrincipalKey(c => c.LicensePlate);      }  }  public class Car  {      public int CarId { get; set; }      public string LicensePlate { get; set; }      public string Make { get; set; }      public string Model { get; set; }        public List<RecordOfSale> SaleHistory { get; set; }  }  public class RecordOfSale  {      public int RecordOfSaleId { get; set; }      public DateTime DateSold { get; set; }      public decimal Price { get; set; }        public string CarLicensePlate { get; set; }      public Car Car { get; set; }  }

下面的程式碼列表演示如何配置複合主體鍵:

class MyContext : DbContext  {      public DbSet<Car> Cars { get; set; }      protected override void OnModelCreating(ModelBuilder modelBuilder)      {          modelBuilder.Entity<RecordOfSale>()              .HasOne(s => s.Car)              .WithMany(c => c.SaleHistory)              .HasForeignKey(s => new { s.CarState, s.CarLicensePlate })              .HasPrincipalKey(c => new { c.State, c.LicensePlate });      }  }  public class Car  {      public int CarId { get; set; }      public string State { get; set; }      public string LicensePlate { get; set; }      public string Make { get; set; }      public string Model { get; set; }        public List<RecordOfSale> SaleHistory { get; set; }  }  public class RecordOfSale  {      public int RecordOfSaleId { get; set; }      public DateTime DateSold { get; set; }      public decimal Price { get; set; }        public string CarState { get; set; }      public string CarLicensePlate { get; set; }      public Car Car { get; set; }  }

10.必需和可選的關係

您可以使用熟知的API來配置是必需的還是可選的關係。最終,這會控制外鍵屬性是必需的還是可選的。當使用陰影狀態外鍵時,這非常有用。如果實體類中具有外鍵屬性,則關係的requiredness取決於外鍵屬性是必需還是可選。

class MyContext : DbContext  {      public DbSet<Blog> Blogs { get; set; }      public DbSet<Post> Posts { get; set; }        protected override void OnModelCreating(ModelBuilder modelBuilder)      {          modelBuilder.Entity<Post>()              .HasOne(p => p.Blog)              .WithMany(b => b.Posts)              .IsRequired();      }  }  public class Blog  {      public int BlogId { get; set; }      public string Url { get; set; }        public List<Post> Posts { get; set; }  }  public class Post  {      public int PostId { get; set; }      public string Title { get; set; }      public string Content { get; set; }        public Blog Blog { get; set; }  }

11.級聯刪除

您可以使用熟知的API顯式配置給定關係的級聯刪除行為。

class MyContext : DbContext  {      public DbSet<Blog> Blogs { get; set; }      public DbSet<Post> Posts { get; set; }      protected override void OnModelCreating(ModelBuilder modelBuilder)      {          modelBuilder.Entity<Post>()              .HasOne(p => p.Blog)              .WithMany(b => b.Posts)              .OnDelete(DeleteBehavior.Cascade);      }  }  public class Blog  {      public int BlogId { get; set; }      public string Url { get; set; }        public List<Post> Posts { get; set; }  }  public class Post  {      public int PostId { get; set; }      public string Title { get; set; }      public string Content { get; set; }        public int? BlogId { get; set; }      public Blog Blog { get; set; }  }

12.其他關係模式

12.1一對一

一對多關係在兩側都有一個引用導航屬性。它們遵循與一對多關係相同的約定,但在外鍵屬性上引入了唯一索引,以確保只有一個依賴項與每個主體相關。

12.1.1數據注釋
public class Blog  {      public int BlogId { get; set; }      public string Url { get; set; }        public BlogImage BlogImage { get; set; }  }  public class BlogImage  {      public int BlogImageId { get; set; }      public byte[] Image { get; set; }      public string Caption { get; set; }        public int BlogId { get; set; }      public Blog Blog { get; set; }  }

12.1.2Fluent API

使用API 配置關係時,請使用HasOne和WithOne方法。配置外鍵時,需要指定依賴實體類型,請注意以下列表HasForeignKey中提供的泛型參數。在一對多關係中,可以清楚地表明具有引用導航的實體是依賴項,並且具有集合的實體是主體。但這並不是一對一的關係,因此需要顯式定義它。

class MyContext : DbContext  {      public DbSet<Blog> Blogs { get; set; }      public DbSet<BlogImage> BlogImages { get; set; }      protected override void OnModelCreating(ModelBuilder modelBuilder)      {          modelBuilder.Entity<Blog>()              .HasOne(p => p.BlogImage)              .WithOne(i => i.Blog)              .HasForeignKey<BlogImage>(b => b.BlogForeignKey);      }  }  public class Blog  {      public int BlogId { get; set; }      public string Url { get; set; }        public BlogImage BlogImage { get; set; }  }  public class BlogImage  {      public int BlogImageId { get; set; }      public byte[] Image { get; set; }      public string Caption { get; set; }        public int BlogForeignKey { get; set; }      public Blog Blog { get; set; }  }

12.2多對多

目前尚不支援多對多關係,沒有實體類來表示聯接表。但是,您可以通過包含聯接表的實體類並映射兩個不同的一對多關係,來表示多對多關係。

class MyContext : DbContext  {      public DbSet<Post> Posts { get; set; }      public DbSet<Tag> Tags { get; set; }      protected override void OnModelCreating(ModelBuilder modelBuilder)      {          modelBuilder.Entity<PostTag>()              .HasKey(pt => new { pt.PostId, pt.TagId });          modelBuilder.Entity<PostTag>()              .HasOne(pt => pt.Post)              .WithMany(p => p.PostTags)              .HasForeignKey(pt => pt.PostId);          modelBuilder.Entity<PostTag>()              .HasOne(pt => pt.Tag)              .WithMany(t => t.PostTags)              .HasForeignKey(pt => pt.TagId);      }  }  public class Post  {      public int PostId { get; set; }      public string Title { get; set; }      public string Content { get; set; }        public List<PostTag> PostTags { get; set; }  }  public class Tag  {      public string TagId { get; set; }        public List<PostTag> PostTags { get; set; }  }  public class PostTag  {      public int PostId { get; set; }      public Post Post { get; set; }        public string TagId { get; set; }      public Tag Tag { get; set; }  }

參考文獻:
關係