EF7建立模型入門篇

2023-03-06 15:01:03

在EF7中,建立一個模型是非常重要的步驟。本文將使用微軟官方檔案中的指南,來學習EF7中的建立模型篇,外加一點點個人理解。

實體型別

在 EF7 中,你需要使用 modelBuilder.Entity() 方法來告訴 EF7 你要包含哪些型別。預設情況下,EF7 會將實體型別的名稱設定為表的名稱。但是,你可以使用 ToTable() 方法來覆蓋預設行為。
如果你的資料庫中有多個模式(schema),你可以使用 ToTable() 方法的另一個過載版本來指定表所屬的架構。如果你想要為生成的表新增註釋,可以使用 HasComment() 方法。如果你不想將某個類對映到資料庫中的表。我們可以使用 modelBuilder.Entity().Ignore() 來排除它。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
		.ToTable("Blogs", schema: "dbo") //.ToTable("Blogs");
		.HasComment("This table contains blog posts.");
	modelBuilder.Ignore<Address>();
}

共用型別實體型別

在 EF7 中,你可以將一個型別對映到多個表中。這種情況通常發生在你有一組具有相似屬性的型別,這些屬性在不同的表中都需要使用。在這種情況下,你可以使用 ModelBuilder.SharedTypeEntity() 方法來建立一個實體型別,並將其對映到多個表中。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var addressEntity = modelBuilder.SharedTypeEntity<Address>("Address");
    addressEntity.ToTable("CustomerBillingAddresses");
    addressEntity.ToTable("CustomerShippingAddresses");

    modelBuilder.Entity<Customer>()
        .OwnsOne(c => c.BillingAddress, b =>
        {
            b.WithOwner().HasForeignKey("BillingAddressId");
            b.ToTable("CustomerBillingAddresses");
        });

    modelBuilder.Entity<Customer>()
        .OwnsOne(c => c.ShippingAddress, b =>
        {
            b.WithOwner().HasForeignKey("ShippingAddressId");
            b.ToTable("CustomerShippingAddresses");
        });
}

在上面的程式碼中,我們首先使用 ModelBuilder.SharedTypeEntity() 方法建立一個名為 Address 的實體型別。然後,我們使用 ToTable() 方法將該實體型別對映到多個表中。接下來,我們使用 OwnsOne() 方法來將 BillingAddressShippingAddress 屬性對映到具有相應名稱的表中。注意,我們還使用了 HasForeignKey() 方法來指定外來鍵的名稱。
使用共用型別實體型別可以使你的程式碼更加簡潔,並提高可維護性。通過使用共用型別實體型別,你可以將一個型別對映到多個表中,而不必在每個實體型別中都定義相同的對映程式碼。

實體屬性

如果要排除實體屬性,可以使用Ignore()方法。

modelBuilder.Entity<Person>().Ignore(p => p.Age)

定義列名。例如,下面的程式碼將為Person類中的LastName屬性定義列名。

modelBuilder.Entity<Person>().Property(p => p.LastName).HasColumnName("Last_Name")

定義列註釋。例如,下面的程式碼將為Person類中的LastName屬性定義註釋。

modelBuilder.Entity<Person>().Property(p => p.LastName).HasComment("The last name of the person")

定義列排序規則。例如,下面的程式碼將為Person類中的LastName屬性定義排序規則。

modelBuilder.Entity<Person>().Property(p => p.LastName).UseCollation("SQL_Latin1_General_CP1_CI_AS");

定義列的資料型別(和資料庫一致即可)。例如,下面的程式碼將為Person類中的Age屬性定義為int型別:

modelBuilder.Entity<Person>().Property(p => p.Age).HasColumnType("int");

定義列的最大長度。例如,下面的程式碼將為Person類中的FirstName屬性定義為50個字元的最大長度:

modelBuilder.Entity<Person>().Property(p => p.FirstName).HasMaxLength(50);

定義列的精度和小數位數。例如,下面的程式碼將為Person類中的Height屬性定義為2位小數的精度:

modelBuilder.Entity<Person>().Property(p => p.Height).HasPrecision(5, 2);

定義是否為必需或可選屬性。例如,下面的程式碼將為Person類中的FirstName屬性定義為必需屬性:

modelBuilder.Entity<Person>().Property(p => p.FirstName).IsRequired();

定義列在表中的順序。例如,下面的程式碼將為Person類中的FirstName屬性定義為表中第二個列:

modelBuilder.Entity<Person>().Property(p => p.Id).HasColumnOrder(1);
modelBuilder.Entity<Person>().Property(p => p.FirstName).HasColumnOrder(2);

主鍵

定義主鍵。根據約定,名為 Id 或 <型別名稱>Id 的屬性將被設定為實體的主鍵。

internal class User
{
    public string Id { get; set; } // 主鍵
    public string Name { get; set; }
}

如果我們不想使用預設約定規則,可以自定義規則。下面的程式碼將指定User類的Id屬性作為主鍵並且重寫設定主鍵的名稱:

modelBuilder.Entity<Person>().HasKey(p => p.Id).HasName("UserId");
注意主鍵Id應是有序的

在 MySQL 中,主鍵 Id 不是有順序的時候,可能會導致新增效能下降的原因是,MySQL 預設使用 B-tree 索引來實現主鍵索引,如果主鍵 Id 是無序的,那麼在插入資料時,MySQL 需要不斷尋找合適的位置來插入新資料,這可能會導致 B-tree 索引不斷被調整,從而影響插入效能。相反,如果主鍵 Id 是有序的,MySQL 可以更快速地找到要插入的位置,從而提高插入效能。

注意主鍵Id應該是最後生成的

有些程式可能會有延遲,導致資料庫插入是非有序的。場景:假如我們先生成Id再處理業務邏輯。有兩個執行緒,同時並行請求。第一個執行緒生成好Id 是 3,處理下面業務邏輯時發生了大概五毫秒的延遲。第二個執行緒也生成好了Id是 4,處理下面業務邏輯時沒有延遲,就通過了。所以第二個執行緒,先進資料庫插入Id為4。第一個執行緒因為有延遲來慢了一步,插入的Id是3。資料庫Id就變成無序的了。

使用基於時間戳的有序Guid作為主鍵

對於非複合數位和 GUID 主鍵,EF Core 根據約定設定值生成。

EF7中 Guid 是基於時間的演演算法精確到納秒。因為一毫秒等於一百萬納秒,所以EF7的Guid一毫秒可以產生一百萬的Id。
優點:不可預測、有序(新增效能高)、在支援Guid(uuid)的資料庫中(查詢效能高)、開箱即用。
缺點:(這是可以忽略不計的事情)並行中在同一納秒內,產生的Id是會重複的。有時鐘回撥問題。太長。
EF7中的Guid有序演演算法比雪花演演算法更好。

  1. 雪花演演算法在並行時,也會重複。因為序列號和時間戳,即使我們設定正確了WorkId。不信你可以寫個例子,思路是:10個並行同時生成Id,儲存到安全執行緒字典中。重複就報個錯。
  2. 雪花演演算法需要額外維護WorkId的工作。
  3. 有時鐘回撥問題。

Guid生成原始碼地址:MySqlSequentialGuidValueGenerator.cs SqlServerSequentialGuidValueGenerator.cs
使用方式:EF7中:建立模型,生成的值。(***後面會繼續講到)

複合鍵

複合鍵是指將多個列作為主鍵的一種設計模式,這些列在組合起來時才能唯一標識一條記錄。相對於單一鍵,複合鍵更加靈活,可以更準確地描述實體之間的關係。例如,在一個訂單系統中,一個訂單可能由多個產品組成,此時可以使用複合鍵來標識訂單編號和產品編號的組合,以確保每個訂單中的每個產品都是唯一的。
使用複合鍵的優點主要有兩點。首先,它提供了更準確的資料描述,特別是在處理多對多關係時,可以更準確地表示關係的唯一性。其次,使用複合鍵可以提高查詢效率,因為複合鍵可以利用資料庫的索引機制,快速定位和存取資料。
然而,複合鍵也存在一些缺點。首先,它增加了開發的複雜性,需要更多的設計和規劃。其次,在使用ORM框架時,如EF7,複合鍵的使用需要特殊的處理,例如在Fluent API中進行設定。最後,複合鍵在某些情況下可能會導致效能問題,例如在大型資料庫中,使用複合鍵可能會影響查詢效能。
在使用EF7時,可以通過Fluent API來設定複合鍵。以一個使用者角色表為例,可以使用以下程式碼定義一個由使用者Id和角色Id的複合主鍵:

internal class UserRole
{
    public string UserId { get; set; } 
    public string RoleId { get; set; }
}
public class MyContext : DbContext
{
    public DbSet<Person> People { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<UserRole>().HasKey(c => new { c.UserId, c.RoleId });
    }
}

在這個範例中,使用Fluent API中的HasKey方法來定義複合主鍵。由於複合鍵是由多個屬性組成的,因此需要將它們放在一個匿名型別中作為引數傳遞給HasKey方法。

注意合理的複合鍵會提高效能。

MySql為例:當使用複合鍵並且確保關聯資料都已經存在時,插入新資料時可能不會對效能產生太大影響。這是因為在 MySQL 中,使用複合鍵時,MySQL 會同時使用所有列生成 B-tree 索引,從而提高查詢和插入的效能。在插入資料時,如果已經確保了關聯資料的存在,那麼 MySQL 可以更快速地插入新資料並生成新的索引,從而提高插入效能。
但是,需要注意,如果你的表結構複雜,或者在插入資料時沒有正確使用索引,那麼複合鍵仍然可能會影響插入效能。

備選鍵

為什麼要使用EF7備選鍵?
在資料庫中,主鍵通常用於唯一標識和檢索實體物件。但是,有些情況下可能需要使用備選鍵來標識實體物件。例如,當主鍵不適合用作某些查詢時,使用備選鍵可以提高資料庫的效能和靈活性。
優點:

  1. 提高效能:使用備選鍵可以減少複雜查詢中的連線數量和查詢時間,從而提高資料庫的效能。
  2. 增強靈活性:備選鍵允許使用其他屬性作為查詢條件,這樣就可以更靈活地查詢資料庫中的實體物件。
  3. 減少衝突:使用備選鍵可以避免主鍵衝突的情況,尤其是在多個實體物件使用同一主鍵的情況下。

缺點:

  1. 增加複雜性:使用備選鍵會增加程式碼的複雜性,因為需要額外的設定和程式碼來實現備選鍵的功能。
  2. 增加維護成本:使用備選鍵會增加資料庫的維護成本,因為需要更多的索引和查詢,以及更多的程式碼來處理備選鍵。
  3. 影響資料完整性:如果備選鍵沒有正確設定,可能會導致資料完整性的問題,因為重複的備選鍵可能會導致資料重複或丟失。

為什麼有這些優點和缺點?
優點是因為備選鍵可以提供更靈活、更高效的查詢和更少的主鍵衝突。缺點是因為使用備選鍵需要更多的設定和程式碼,並且可能會影響資料完整性。
以下是一個簡單的範例,演示如何使用備選鍵:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

public class MyContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
		// 將Email屬性指定為備選鍵
        modelBuilder.Entity<Person>().HasAlternateKey(p => p.Email); 
    }
}

複合備選鍵

為什麼要使用複合備選鍵?
使用複合備選鍵的一個重要原因是在某些情況下,單個列可能不足以唯一標識表中的每一行。例如,在一個電影資料庫中,如果只使用電影名稱作為主鍵,則會出現多個電影名稱相同的情況。因此,需要使用複合備選鍵,通過多個列來確定每個電影的唯一性。
優點

  1. 更加靈活的資料建模:複合備選鍵使得資料建模更加靈活,可以在表中使用多個列來確定唯一性。這使得資料建模更加符合實際情況,可以更好地支援複雜的業務場景。
  2. 更好的效能:使用複合備選鍵可以提高資料庫的效能。這是因為使用多個列來唯一標識行,可以減少在表中的掃描次數,從而提高查詢效能。
  3. 更好的資料完整性:使用複合備選鍵可以更好地保證資料完整性。在使用複合備選鍵的表中,每個行的唯一性都由多個列決定,這使得在資料插入和更新時,更難發生資料衝突和錯誤。

缺點

  1. 複雜性:使用複合備選鍵會增加表的複雜性。需要在表中定義多個列,以確定唯一性。此外,在查詢時,需要指定多個列作為條件,以獲取唯一的行。這可能會增加程式碼的複雜性,需要更加謹慎地編寫程式碼。
  2. 不支援自動增長:使用複合備選鍵時,不支援自動增長功能。這是因為每個行的唯一性都是由多個列來決定的,如果一個列是自動增長的,就不能保證每一行都是唯一的。

使用方式
使用Fluent API是定義複合備選鍵的最佳方式。以下是使用Fluent API定義複合備選鍵的範例:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
}

public class PersonContext : DbContext
{
    public DbSet<Person> Persons { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>()
			// 將多個屬性設定為備選鍵(即複合備選鍵)
			.HasAlternateKey(p => new { p.FirstName, p.LastName, p.DateOfBirth })
			// 可設定備選鍵的索引和唯一約束的名稱:
			.HasName("FirstName_LastName_DateOfBirth");
    }
}

分組設定模型

可以使用分組設定,這樣可以將實體和關係的設定組織在一起,使程式碼更具可讀性。例如:

public class BlogConfiguration : IEntityTypeConfiguration<Blog>
{
    public void Configure(EntityTypeBuilder<Blog> builder)
    {
        builder.HasKey(x => x.BlogId);
        builder.Property(x => x.Name).IsRequired();
    }
}

然後在DbContext中使用以下程式碼將此設定應用於Blog實體:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.ApplyConfiguration(new BlogConfiguration());
}

使用資料註釋來設定模型

可以使用資料註釋來設定EF7模型。資料註釋是一種以屬性和類註釋的方式來提供後設資料的方法。例如:

public class Blog
{
    [Key]
    public int BlogId { get; set; }
    [Required]
    public string Name { get; set; }
}

在這個範例中,我們使用了Key和Required註釋來指定BlogId和Name屬性的主鍵和IsRequired標誌。

內建約定

除了手動設定外,EF7還提供了一些內建約定,可以根據慣例自動推斷模型。例如,如果實體中有一個名為Id的屬性,EF7會將其作為主鍵。如果實體之間具有參照關係,EF7會自動建立外來鍵。

刪除現有約定

如果不想使用內建約定,可以通過呼叫ModelBuilder.Conventions.Remove方法來刪除它們。例如,以下程式碼刪除了為外來鍵列建立索引的約定:

modelBuilder.Conventions.Remove<ForeignKeyIndexConvention>();

總結:

在本文中,介紹瞭如何告訴EF7使用實體型別和過濾型別,並且還說了共用實體型別。我們還介紹了實體屬性的設定。還介紹了四種鍵。介紹了使用Fluent API和資料註釋來設定EF7模型。我們還了解了EF7的內建約定,並學習瞭如何刪除現有約定。使用這些技術,可以輕鬆地建立和設定EF7模型,並更好地管理資料庫存取程式碼。