背景 公司里的某負責保存用戶文檔的子系統有時會忽然cpu很高,過了大約5分鐘後又恢復正常水平。領導協調讓我幫看一下 (我心裡是: 不熟悉這個子系統裡面的代碼,我儘力哈😓) 其實確實是這樣的,如果熟悉出問題的系統的代碼,會對診斷問題起到很大的幫助,否則就需要更多的利用對底層的理解了。 分析 打聽後知 ...
在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模型,並更好地管理資料庫訪問代碼。