在EF7中,建立一個模型是非常重要的步驟。本文將使用微軟官方檔案中的指南,來學習EF7中的建立模型篇,外加一點點個人理解。
實體型別
在 EF7 中,你需要使用 modelBuilder.Entity
如果你的資料庫中有多個模式(schema),你可以使用 ToTable() 方法的另一個過載版本來指定表所屬的架構。如果你想要為生成的表新增註釋,可以使用 HasComment() 方法。如果你不想將某個類對映到資料庫中的表。我們可以使用 modelBuilder.Entity
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() 方法來將 BillingAddress 和 ShippingAddress 屬性對映到具有相應名稱的表中。注意,我們還使用了 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有序演演算法比雪花演演算法更好。
- 雪花演演算法在併發時,也會重複。因為序列號和時間戳,即使我們配置正確了WorkId。不信你可以寫個例子,思路是:10個併發同時生成Id,儲存到安全執行緒字典中。重複就報個錯。
- 雪花演演算法需要額外維護WorkId的工作。
- 有時鐘回撥問題。
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備選鍵?
在資料庫中,主鍵通常用於唯一標識和檢索實體物件。但是,有些情況下可能需要使用備選鍵來標識實體物件。例如,當主鍵不適合用作某些查詢時,使用備選鍵可以提高資料庫的效能和靈活性。
優點:
- 提高效能:使用備選鍵可以減少複雜查詢中的連線數量和查詢時間,從而提高資料庫的效能。
- 增強靈活性:備選鍵允許使用其他屬性作為查詢條件,這樣就可以更靈活地查詢資料庫中的實體物件。
- 減少衝突:使用備選鍵可以避免主鍵衝突的情況,尤其是在多個實體物件使用同一主鍵的情況下。
缺點:
- 增加複雜性:使用備選鍵會增加程式碼的複雜性,因為需要額外的配置和程式碼來實現備選鍵的功能。
- 增加維護成本:使用備選鍵會增加資料庫的維護成本,因為需要更多的索引和查詢,以及更多的程式碼來處理備選鍵。
- 影響資料完整性:如果備選鍵沒有正確配置,可能會導致資料完整性的問題,因為重複的備選鍵可能會導致資料重複或丟失。
為什麼有這些優點和缺點?
優點是因為備選鍵可以提供更靈活、更高效的查詢和更少的主鍵衝突。缺點是因為使用備選鍵需要更多的配置和程式碼,並且可能會影響資料完整性。
以下是一個簡單的示例,演示如何使用備選鍵:
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);
}
}
複合備選鍵
為什麼要使用複合備選鍵?
使用複合備選鍵的一個重要原因是在某些情況下,單個列可能不足以唯一標識表中的每一行。例如,在一個電影資料庫中,如果只使用電影名稱作為主鍵,則會出現多個電影名稱相同的情況。因此,需要使用複合備選鍵,透過多個列來確定每個電影的唯一性。
優點
- 更加靈活的資料建模:複合備選鍵使得資料建模更加靈活,可以在表中使用多個列來確定唯一性。這使得資料建模更加符合實際情況,可以更好地支援複雜的業務場景。
- 更好的效能:使用複合備選鍵可以提高資料庫的效能。這是因為使用多個列來唯一標識行,可以減少在表中的掃描次數,從而提高查詢效能。
- 更好的資料完整性:使用複合備選鍵可以更好地保證資料完整性。在使用複合備選鍵的表中,每個行的唯一性都由多個列決定,這使得在資料插入和更新時,更難發生資料衝突和錯誤。
缺點
- 複雜性:使用複合備選鍵會增加表的複雜性。需要在表中定義多個列,以確定唯一性。此外,在查詢時,需要指定多個列作為條件,以獲取唯一的行。這可能會增加程式碼的複雜性,需要更加謹慎地編寫程式碼。
- 不支援自動增長:使用複合備選鍵時,不支援自動增長功能。這是因為每個行的唯一性都是由多個列來決定的,如果一個列是自動增長的,就不能保證每一行都是唯一的。
使用方式
使用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模型,並更好地管理資料庫訪問程式碼。