EntityFramework Core 學習掃盲

来源:http://www.cnblogs.com/Wddpct/archive/2017/05/10/6835574.html
-Advertisement-
Play Games

0. 寫在前面 1. 建立運行環境 2. 添加實體和映射資料庫 1. 準備工作 2. Data Annotations 3. Fluent Api 3. 包含和排除實體類型 1. Data Annotations [NotMapped] 排除實體和屬性 2. Fluent API [Ignore] ...


0. 寫在前面

本篇文章雖說是入門學習,但是也不會循規蹈矩地把EF1.0版本一直到現在即將到來的EF Core 2.0版本相關的所有歷史和細節完完整整還原出來。在後文中,筆者會直接進入正題,所以這篇文章仍然還是需要一定的EF ORM基礎。

對於純新手用戶,不妨先去看看文末鏈接中一些優秀博客,筆者當初也是從這些博客起家,也從中得到了巨大的幫助。當然了,官方教程同樣至關重要,筆者之前也貢獻過部分EF CORE 官方文檔資料(基本都是勘誤,逃…),本篇文章中很多內容都是擷取自官方的英文文檔和示例。

下文示例中將使用Visual Studio自帶的Local Sql Server作為演示資料庫進行演示,不過可以放心的是,大部分示例都能流暢地在各種關係型資料庫中實現運行,前提是更換不同的DATABASE PROVIDERS,如NPGSQLMYSQL等。

對於未涉及到的知識點(CLI工具,Shadow Property,Logging,從Exsiting Database反向工程生成Context等),只能說筆者最近一直在忙著畢業收尾的事情,有空的時候會把草稿整理下在博文中貼出的,一晃四年,終於也要畢業了。

1. 建立運行環境

  • 新建一個APS.NET CORE WEB模板項目

  • 安裝相關Nuget包

//Sql Server Database Provider
Install-Package Microsoft.EntityFrameworkCore.SqlServer
    
//提供熟悉的Add-Migration,Update-Database等Powershell命令,不區分關係型資料庫類型
Install-Package Microsoft.EntityFrameworkCore.Tools 
  • 自定義Context
public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options):base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    }
}
  • 在Startup的ConfigurationServices方法中添加EF CORE服務
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
        
// 需要在appsettings.json中新增一個ConnectionStrings節點,用於存放連接字元串。
"ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication4;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  • 在powershell中運行相關遷移命令
Add-Migration Initialize

Update-Database

2. 添加實體和映射資料庫

使用EF CORE中添加實體,約束屬性和關係,最後將其映射到資料庫中的方式有兩種,一種是Data Annotations,另一種是Fluent Api,這兩種方式並沒有優劣之分,全憑開發者喜好和需求,不過相對而言,Fluent Api提供的功能更多。

1. 準備工作

  • 新增三個實體
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; }
}

public class AuditEntry
{
    public int AuditEntryId { get; set; }
    public string Username { get; set; }
    public string Action { get; set; }
}

2. Data Annotations

  • 在自定義的MyContext中添加以下屬性信息,併在每個自定義的實體名稱上部增加[Table("XXX")],其中XXX為開發者指定的表名稱。
//在自定義的MyContext中添加以下三行代碼
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<AuditEntry> AuditEntries { get; set; }

//添加Table特性,第一個屬性代表資料庫表名稱
[Table("Blogs")]
public class Blog
{
    [Key]
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}
  • 運行Add-Migration InitializeUpdate-Database即可成功運行。雖然我們目前還沒有添加任何約束,但是EF Core會自動地根據Id/XXId的命名方式生成自增主鍵,而且如果沒有在實體上增加[Table]Attribute的話,表的命名也是根據屬性命名而定。查詢相關的Create Table語句,清晰易見,Identity(1,1)代表Id從1開始,每次插入遞增1。
//BLOG Table
CREATE TABLE [dbo].[Blogs] (
    [BlogId] INT            IDENTITY (1, 1) NOT NULL,
    [Url]    NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_Blogs] PRIMARY KEY CLUSTERED ([BlogId] ASC)
);

CREATE TABLE [dbo].[Posts] (
    [PostId]  INT            IDENTITY (1, 1) NOT NULL,
    [BlogId]  INT            NULL,
    [Content] NVARCHAR (MAX) NULL,
    [Title]   NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY CLUSTERED ([PostId] ASC),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [dbo].[Blogs] ([BlogId])
);

//POST Table
GO
CREATE NONCLUSTERED INDEX [IX_Posts_BlogId]
    ON [dbo].[Posts]([BlogId] ASC);

3. Fluent Api

Fluent Api俗名流式介面,其實就是C#中的擴展介面形式而已,大家日常應該接觸過很多了。還記得我們第一步中MyContext定義的OnModelCreating方法嗎,Fluent Api就是在那裡面使用的

  • 增加以下代碼至OnModelCreating方法。
modelBuilder.Entity<Blog>().ToTable("Blogs").HasKey(c=>c.BlogId);
modelBuilder.Entity<Post>().ToTable("Posts").HasKey(c=>c.PostId);
modelBuilder.Entity<AuditEntry>().ToTable("AuditEntries").HasKey(c=>c.AuditEntryId);
  • 運行Add-Migration InitializeUpdate-Database即可成功運行。

無論是使用DbSet< TEntity >的形式抑或是使用modelBuilder.Entity< TEntity >的形式都能將定義的實體映射到資料庫中,下文也會繼續做出說明。

3. 包含和排除實體類型

將實體在Context中映射到資料庫有多種方式:

  • 使用DbSet< TEntity >定義屬性。
  • 在OnModelCreating方法中使用Fluent Api配置。
  • 假如導航屬性中存在對其他實體的引用,那麼即便不把被引用實體配置為顯式引用,被引用實體也可以隱式地映射到資料庫中。

如以下代碼所示。Blog實體包含對Post實體的引用,而獨立的AuditEntry則可以在OnModelCreating方法中進行配置。

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<AuditEntry>();
    }
}

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

public class AuditEntry
{
    public int AuditEntryId { get; set; }
    public string Username { get; set; }
    public string Action { get; set; }
}

以上內容指的是“包含實體”的操作,“排除實體”操作也十分地簡便。通過以下兩種配置方式,在運行了遷移命令後,BlogMetadata實體是不會映射到資料庫中的。

1. Data Annotations [NotMapped] 排除實體和屬性

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogMetadata Metadata { get; set; }
}

[NotMapped]
public class BlogMetadata
{
    public DateTime LoadedFromDatabase { get; set; }
}

2. Fluent API [Ignore] 排除實體和屬性

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Ignore<BlogMetadata>();
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogMetadata Metadata { get; set; }
}

public class BlogMetadata
{
    public DateTime LoadedFromDatabase { get; set; }
}

NotMapped特性不僅可以用在實體類上,也可以用在指定的屬性上。而在Fluent Api中,對應的操作則是
modelBuilder.Entity<Blog>().Ignore(b => b.LoadedFromDatabase);

4. 列名稱和類型映射

Property方法對應資料庫中的Column。

預設情況下,我們不需要更改任何實體中包含的屬性名,EF CORE會自動地根據屬性名稱映射到資料庫中的列名。當開發者需要進行自定義修改名稱時( 比如每種關係型資料庫的命名規則不一樣,雖然筆者一直喜歡使用帕斯卡命名以保持和項目代碼結構中的統一),可以使用以下的方式。

少數的幾個CLR類型在不做處理的情況下,映射到資料庫中時將存在可空選項,如string,int?,這種情況也在下列方式中做了說明。其中Data Annotations對應[Required]特性,Fluent API對應IsRequired方法。

1. Data Annotations

Column特性可用於屬性上,它接收多個參數,其中比較重要的是Name和TypeName,前者表示資料庫表映射的列名,後者表示數據類型和格式。假如不指定Url屬性上的[Column(TypeName="varchar(200)")],資料庫Blog表的Url列預設數據格式將為varchar(max)

public class Blog
{
    [Column("blog_id")]
    public int BlogId { get; set; }
    
    [Required]
    [Column(TypeName = "varchar(200)")]
    public string Url { get; set; }
}

2. Fluent API

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.BlogId)
            .HasColumnName("blog_id");
            
            modelBuilder.Entity<Blog>()
            .Property(b => b.Url)
            .HasColumnType("varchar(200)")
            .IsRequired();
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

由於各種關係型資料庫對於數據類型的名稱有所區別,所以自定義數據類型時,一定要參閱目標資料庫的數據類型定義。比如PostgreSql支持Json格式,那麼就需要添加以下代碼——builder.Entity().Property(b => b.SomeStringProperty).HasColumnType("jsonb");

5. 主鍵

預設情況下,EF CORE會將實體中命名為Id或者[TypeName]Id的屬性映射為資料庫表中的主鍵。當然有些開發者不喜歡將主鍵命名為Id,EF CORE也提供了兩種方式進行主鍵的相關設置。

1. Data Annotations [Key]

Data Annotations方式不支持定義外鍵名稱,主鍵配置如下代碼所示。

class Car
{
    [Key]
    public string LicensePlate { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}

2. Fluent API [HasKey]

Fluent Api方式中的HasKey方法可以將屬性映射為主鍵,對於複合主鍵(多個屬性組合而成的主鍵標識)也可以很容易地進行表示。

class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Car>()
            .HasKey(c => c.LicensePlate);
        
        //複合主鍵
        //modelBuilder.Entity<Car>()
            //.HasKey(c => new { c.State, c.LicensePlate });
            
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey(p => p.BlogId)
            .HasConstraintName("ForeignKey_Post_Blog");
    }
}

class Car
{
    public string State { get; set; }
    public string LicensePlate { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}

6. 備用鍵

Alternate Keys是EF CORE引入的新功能,EF 6.X版本中並沒有此功能。備用鍵可以用作實體中除主鍵和索引外的唯一標識符,還可以用作外鍵目標。在Fluent Api中,有兩種方法可以指定備用鍵,一種是當開發者將實體中的屬性作為另一個實體的外鍵目標,另一種是手動指定。EF CORE的預設約束是前者。

備用鍵和主鍵的作用十分相似,同樣也存在複合備用鍵的功能,請大家註意區分。在要求單表列的一致性的場景中,使用唯一索引比使用備用鍵更佳。

1. Fluent API

public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options) : base(options)
    {
    }

    public DbSet<Car> Cars { get; set; }
    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.BlogUrl)
            .HasPrincipalKey(b => b.Url);
            
        // 第二種方法
        modelBuilder.Entity<Car>()
            .HasAlternateKey(c => c.LicensePlate)
            .HasName("AlternateKey_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 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 string BlogUrl { get; set; }
    public Blog Blog { get; set; }
}

上述代碼中的第一種方法指定Post實體中的BlogUrl屬性作為Blog對應Post的外鍵,指定Blog實體中的Url屬性作為備用鍵(HasPrincipalKey方法將在下文的唯一標識節中講解),此時Url將被配置為唯一列,扮演BlogId的作用。

7. 計算列

計算列指的是列的數據由資料庫計算生成,在EF CORE層面,我們只需要定義計算規則即可。目前EF CORE 1.1 版本中,暫不支持使用Data Annotations方式定義。

1 Fluent API

class MyContext : DbContext
{
    public DbSet<Person> People { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>()
            .Property(p => p.DisplayName)
            .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
    }
}

public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string DisplayName { get; set; }
}

以上代碼指明DisplayName由LastName和FirstName結合計算而成,這項工作由資料庫代勞,查看P的視圖設計器,我們也可以發現資料庫在生成表時便指定了詳細規則。

CREATE TABLE [dbo].[People] (
    [PersonId]    INT            IDENTITY (1, 1) NOT NULL,
    [DisplayName] AS             (([LastName]+', ')+[FirstName]),
    [FirstName]   NVARCHAR (MAX) NULL,
    [LastName]    NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_People] PRIMARY KEY CLUSTERED ([PersonId] ASC)
);

8. 生成值

前文中已經介紹過,假如屬性被命名為Id/[TypeName]Id的形式,EF CORE會將該屬性設置為主鍵。進一步說,如果屬性是整數或是Guid類型,那麼該屬性將會被EF CORE設置為自動生成。這是EF CORE的語法糖之一。

那由用戶手動設置呢?EF CORE在Data Annotations和Fluent Api形式上為開發者分別提供了三種方法。

1 Data Annotations

  • 不生成(預設方式)
public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int BlogId { get; set; }
    public string Url { get; set; }
}
  • 新增實體信息時自動添加
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public DateTime Inserted { get; set; }
}
  • 新增或更新實體時自動添加
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime LastUpdated { get; set; }
}

2 Fluent API

  • 不生成(預設方式)
modelBuilder.Entity<Blog>()
    .Property(b => b.BlogId)
    .ValueGeneratedNever();
  • 新增實體信息時自動添加
modelBuilder.Entity<Blog>()
    .Property(b => b.Inserted)
    .ValueGeneratedOnAdd();
  • 新增或更新實體時自動添加
modelBuilder.Entity<Blog>()
    .Property(b => b.LastUpdated)
    .ValueGeneratedOnAddOrUpdate();

值得註意的是,上述對DateTime類型的自動添加操作都是不可行的,這是因為EF CORE只支持部分類型的自動操作,詳見Default Values。對於DateTime類型,我們可以用以下代碼實現自動插入
modelBuilder.Entity<Blog>().Property(b => b.Created).HasDefaultValueSql("getdate()");
這也是第7點預設值的一種用法。

9. 預設值

預設值與計算列定義十分相似,只是計算列無法由用戶手動輸入。而預設值更多指的是當用戶不手動輸入時,使用預設值進行資料庫相應列的填充。以下代碼表示假如操作中不指定Rating的值,那麼資料庫將預設填充3。

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Rating)
            .HasDefaultValue(3);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    public int Rating { get; set; }
}

10. 索引

EF CORE中的索引概念和關係型資料庫中的索引概念沒有什麼不同,比如在Sql Server,將Blog映射到資料庫時,將為BlogId建立主鍵預設持有的聚集索引,將Post映射到資料庫中時,將為Post的BlogId建議外鍵預設的非聚集索引。

GO
CREATE NONCLUSTERED INDEX [IX_Posts_BlogId]
    ON [dbo].[Posts]([BlogId] ASC);

至於為一個或多個屬性手動建立索引,可以使用形如以下代碼。

1. Fluent API

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Person> People { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .HasIndex(b => b.Url)
            .HasName("IX_Url");
        
        modelBuilder.Entity<Person>()
            .HasIndex(p => new { p.FirstName, p.LastName });
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

假如你需要配置一個唯一索引,請使用IsUnique方法。形如以下代碼:

modelBuilder.Entity<Blog>()
            .HasIndex(b => b.Url)
            .HasName("IX_Url")
            .IsUnique();

11. 主體和唯一標識

在這一節中,讓我們來回顧一下HasPrincipalKey方法和唯一標識。

在EF CORE中,主體(Principal Entity)指的是包含主鍵/備用鍵的實體。所以在一般情況下,所有的實體都是主體。而主體鍵(Principal Key)指的是主體中的主鍵/備用鍵。大家都知道,主鍵/備用鍵都是不可為空且唯一的,這就引出了唯一標識列的寫法。

唯一標識列一般有“主體鍵”,“唯一索引”兩種寫法,其中主體鍵中的主鍵沒有什麼討論的價值。讓我們來看看其他兩種的寫法。

1. 備用鍵

備用鍵在之前的小節中已經提過,使用以下代碼配置的列將自動設置為唯一標識列。

modelBuilder.Entity<Car>()
                .HasAlternateKey(c => new {c.LicensePlate, c.Model})
                .HasName("AlternateKey_LicensePlate");
                
modelBuilder.Entity<Post>()
                .HasOne(p => p.Blog)
                .WithMany(b => b.Posts)
                .HasForeignKey(p => p.BlogUrl)
                .HasPrincipalKey(b => b.Url);

註意這裡的HasPrincipalKey方法,它通常跟在HasForeignKey和WithMany方法後,用以指定實體中的一個或多個屬性作為備用鍵。再次重申一遍,備用鍵和主鍵有相似之處,它通常用來指定一個明確的外鍵目標——當開發者不想用單純無意義的Id作為外鍵標識時。

雖然主體鍵也包括主鍵,但是主鍵在EF CORE中時強制定義的,所以HasPrincipalKey只會將屬性配置為備用鍵。

2. 唯一索引

索引及其唯一性只由Fluent Api方式指定,由索引來指定唯一列是比備用鍵更好的選擇。

modelBuilder.Entity<Blog>()
            .HasIndex(b => b.Url)
            .IsUnique();

12. 繼承

繼承通常被用來控制實體類介面如何映射到資料庫表結構中。在EF CORE 當前版本中,TPC和TPT暫不被支持,TPH是預設且唯一的繼承方式。

那麼什麼是TPH(table-per-hierarchy)呢?顧名思義,一種繼承結構全部映射到一張表中,比如Person父類,Student子類和Teacher子類,由EF CORE映射到資料庫中時,將會只存在Person類,而Student和Teacher將以列標識的形式出現。

目前只有Fluent Api方式支持TPH,具體實體類代碼如下,其中RssBlog繼承自Blog。

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

public class RssBlog : Blog
{
    public string RssUrl { get; set; }
}

在MyContext中,我們將原來的代碼修改成如下形式:

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .HasDiscriminator<string>("blog_type")
            .HasValue<Blog>("blog_base")
            .HasValue<RssBlog>("blog_rss");
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

public class RssBlog : Blog
{
    public string RssUrl { get; set; }
}

觀察OnModelCreating方法,HasDiscriminator提供修改標識列名的功能,HasValue提供新增或修改實體時,根據實體類型將不同的標識自動寫入標識列中。如新增Blog時,blog_type列將寫入blog_base字元串,新增RssBlog時,blog_type列將寫入blog_rss字元串。

筆者不推薦用繼承的方式設計資料庫,只是這個功能相對新奇,就列出來說了。

13. 關係

關係型資料庫模型的設計中,最重要的一點便是“關係”的設計了。常見的關係有1-1,1-n,n-n,除此以外,關係的兩邊還有可空不可空的控制。那麼在EF CORE中,我們怎麼實現這些關係呢?

以下內容用代碼的方式給出了一對一,一對多和多對多的關係,兩邊關係設為不可空。其實可空不可空的控制十分簡單,只要註意是否需要加上IsRequired的擴展Api即可。

不得不說,相比EF6.X的HasRequired和WithOptional等方法,EF CORE中的Api和關係配置清晰直觀了不少。

唯一需要註意的是,關係設置請從子端(如User和Blog呈一對多對應時,從Blog開始)開始,否則配置不慎容易出現多個外鍵的情況。

public class MyContext : DbContext
    {
        public MyContext(DbContextOptions<MyContext> options) : base(options)
        {
        }

        public DbSet<User> Users { get; set; }
        public DbSet<UserAccount> UserAccounts { get; set; }
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
        public DbSet<PostTag> PostTags { get; set; }
        public DbSet<Tag> Tags { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Blog與Post之間為 1 - N 關係
            modelBuilder.Entity<Post>()
                .HasOne(p => p.Blog)
                .WithMany(b => b.Posts)
                .HasForeignKey(p => p.BlogId)
                // 使用HasConstraintName方法配置外鍵名稱
                .HasConstraintName("ForeignKey_Post_Blog")
                .IsRequired();

            // User與UserAccount之間為 1 - 1 關係
            modelBuilder.Entity<UserAccount>()
                .HasKey(c => c.UserAccountId);

            modelBuilder.Entity<UserAccount>()
                .HasOne(c => c.User)
                .WithOne(c => c.UserAccount)
                .HasForeignKey<UserAccount>(c => c.UserAccountId)
                .IsRequired();

            // User與Blog之間為 1 - N 關係
            modelBuilder.Entity<Blog>()
                .HasOne(b => b.User)
                .WithMany(c => c.Blogs)
                .HasForeignKey(c => c.UserId)
                .IsRequired();

            // Post與Tag之間為 N - N 關係
            modelBuilder.Entity<PostTag>()
                .HasKey(t => new {t.PostId, t.TagId});

            modelBuilder.Entity<PostTag>()
                .HasOne(pt => pt.Post)
                .WithMany(p => p.PostTags)
                .HasForeignKey(pt => pt.PostId)
                .IsRequired();

            modelBuilder.Entity<PostTag>()
                .HasOne(pt => pt.Tag)
                .WithMany(t => t.PostTags)
                .HasForeignKey(pt => pt.TagId)
                .IsRequired();
        }
    }

    public class Blog
    {
        public int BlogId { get; set; }
        public string Url { get; set; }
        public List<Post> Posts { get; set; }
        public int UserId { get; set; }
        public User User { get; set; }
    }

    public class User
    {
        public int UserId { get; set; }
        public string UserName { get; set; }
        public List<Blog> Blogs { get; set; }
        public UserAccount UserAccount { get; set; }
    }

    public class UserAccount
    {
        public int UserAccountId { get; set; }
        public string UserAccountName { get; set; }
        public bool IsValid { get; set; }
        public User User { 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; }
        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; }
    }

14. 參考鏈接和優秀博客

  1. EF CORE OFFICIAL DOC
  2. Introduction to Entity Framework
  3. Feature ​Comparison
  4. Entity Framework教程(第二版)

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這是NancyFx開源框架中的Basic認證,學習一下! 首先當然是新建一個空的Web,BasicDemo 繼續在項目中添加Nuget包,記得安裝的Nuget包是最新的預發行版 Nancy Nancy.Authentication.Basic Nancy.Hosting.Aspnet 之後就往項目中 ...
  • IP地址 1)網路地址 IP地址由網路號(包括子網號)和主機號組成,網路地址的主機號為全0,網路地址代表著整個網路。 2)廣播地址 廣播地址通常稱為直接廣播地址,是為了區分受限廣播地址。 廣播地址與網路地址的主機號正好相反,廣播地址中,主機號為全1。當向某個網路的廣播地址發送消息時,該網路內的所有主 ...
  • .NET 4 開始,在System.Collection.Concurrent中提供了幾個線程安全的集合類。線程安全的集合可防止多個線程以相互衝突的方式訪問集合。 為了對集合進行線程安全的訪問,定義了IProducerConsumerCollection<T>介面。這個介面中最重要的方法是TryAd ...
  • 學習任何東西,我們只要搞清楚其原理,就會觸類旁通。現在結和我所學,我想總結一下客戶端到伺服器端的通信過程。只有明白了原理,我們才會明白當我們程式開發過程中錯誤的問題會出現在那,才會更好的解決問題。 我們首先要瞭解一個概念性的辭彙:Socket socket的英文原義是“孔”或“插座”。作為進程通信機 ...
  • 1.新建ASP.NET 項目,模板選擇如圖 2.選擇Web API,並選擇不進行身份驗證方式 成功後我們看到這個結果。 至於其它三種身份驗證方式,不太適合我的使用。而且這種方式也可以在代碼里去實現身份驗證,比較符合已有資料庫的情況,這裡不寫。 3.給項目進行配置修改 3.1 修改對應項目生成路徑。 ...
  • public:公有訪問。不受任何限制。private:私有訪問。只限於本類成員訪問,子類和實例都不能訪問。protected:保護訪問。只限於本類和子類訪問,實例不能訪問。internal:內部訪問。只限於本項目(程式集)內訪問,其他不能訪問。protected internal :內部保護訪問。只 ...
  • 1、先附上一份值類型和引用類型各自的成員 2、值類型和引用類型的區別 值類型直接存儲其值,引用類型存儲其值的引用 值類型變數都存儲在堆棧中,引用類型在托管堆中分配存儲單元 值類型變數不能為null,必須有確定的值,引用類型被賦值前的值都是null 值類型是從System.ValueType類繼承而來 ...
  • 如果對象可以改變其狀態,就很難在多個同時運行的任務中使用。這些集合必須同步。如果對象不能改變器狀態,就很容易在多個線程中使用。 Microsoft提供了一個新的集合庫:Microsoft Immutable Collection。顧名思義,它包含不變的集合類————創建後不能改變的集合類。該類在Sy ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...