一.主鍵
鍵用作每個(gè)實(shí)體實(shí)例的主要唯一標(biāo)識(shí)符。 使用關(guān)系數(shù)據(jù)庫(kù)時(shí),這會(huì)映射到主鍵的概念。 還可以配置不是主鍵的唯一標(biāo)識(shí)符。按照約定,名為 Id 或 <type name>Id 的屬性會(huì)配置為實(shí)體的鍵。例如下面二個(gè)示例:
class Car
{
//映射到Car表 Id主鍵
public string Id { get; set; }
}
class Car
{
//映射到Car表CarId主鍵,約定格式:<type name>Id
public string CarId { get; set; }
}
除了上面講到的約定,還可以用數(shù)據(jù)注釋將單個(gè)屬性配置為實(shí)體的鍵,下面示例使用數(shù)據(jù)注釋配置主鍵
class Car
{
//映射到Car表LicensePlate主鍵
[Key]
public string LicensePlate { get; set; }
}
還可以使用 Fluent API 將單個(gè)屬性配置為實(shí)體的鍵。下面示例使用Fluent API配置主鍵
class MyContext : DbContext
{
public DbSet<Car> Cars { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>().HasKey(c => c.LicensePlate);
}
}
class Car
{
public string LicensePlate { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
還可以使用 Fluent API 將多個(gè)屬性配置為實(shí)體的鍵(稱(chēng)為復(fù)合鍵)。 只能使用 Fluent API 配置復(fù)合鍵 - 不能使用約定來(lái)設(shè)置復(fù)合鍵,也不能使用數(shù)據(jù)注釋來(lái)配置復(fù)合鍵。
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 });
}
}
二.生成的值
對(duì)于屬性的值生成模式有三種:(1) 無(wú)值生成;(2) 在新增時(shí)自動(dòng)生成值;(3) 在添加或更新時(shí)自動(dòng)生成值。下面介紹數(shù)據(jù)注釋中使用DatabaseGeneratedOption枚舉來(lái)實(shí)現(xiàn),說(shuō)明這三種生成模式的應(yīng)用場(chǎng)景:
public class Blog
{
//沒(méi)有值生成, 主鍵一般在數(shù)據(jù)庫(kù)中都是自增,所以在EF中不需要給值
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int BlogId { get; set; }
//在新增時(shí)生成值, 一般插入一條數(shù)據(jù)時(shí),記錄插入的時(shí)間
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime Inserted { get; set; }
//在新增或修改時(shí)生成值, 一般記錄修改的時(shí)間。
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime LastUpdated { get; set; }
}
除了用數(shù)據(jù)注釋方法生成值,還可以使用Fluent API用于更改某一給定屬性的值生成模式。
//沒(méi)有值生成
modelBuilder.Entity<Blog>().Property(b => b.BlogId).ValueGeneratedNever();
//在新增時(shí)生成值
modelBuilder.Entity<Blog>().Property(b => b.Inserted).ValueGeneratedOnAdd();
//在新增或修改時(shí)生成值
modelBuilder.Entity<Blog>().Property(b => b.LastUpdated).ValueGeneratedOnAddOrUpdate();
三. 最大長(zhǎng)度
最大長(zhǎng)度僅適用于數(shù)組,數(shù)據(jù)類(lèi)型,如 string 和 byte[]。將數(shù)據(jù)傳遞到提供程序之前,實(shí)體框架不會(huì)執(zhí)行任何最大長(zhǎng)度驗(yàn)證。 由提供程序或數(shù)據(jù)存儲(chǔ)在適當(dāng)時(shí)機(jī)進(jìn)行驗(yàn)證。以 SQL Server 為目標(biāo),超過(guò)最大長(zhǎng)度將引發(fā)異常。
使用數(shù)據(jù)注釋來(lái)配置屬性的最大長(zhǎng)度。 此示例面向 SQL Server,因此使用數(shù)據(jù)類(lèi)型 nvarchar(500)。
public class Blog
{
public int BlogId { get; set; }
[MaxLength(500)]
public string Url { get; set; }
}
使用 Fluent API 配置屬性的最大長(zhǎng)度。 此示例面向 SQL Server,因此使用數(shù)據(jù)類(lèi)型 nvarchar(500)。
modelBuilder.Entity<Blog>().Property(b => b.Url).HasMaxLength(500);
四.并發(fā)標(biāo)記
配置并發(fā)標(biāo)記是用于解決數(shù)據(jù)庫(kù)中的并發(fā)沖突。數(shù)據(jù)庫(kù)并發(fā):指多個(gè)進(jìn)程或用戶同時(shí)訪問(wèn)或更改數(shù)據(jù)庫(kù)中的相同數(shù)據(jù)的情況。 并發(fā)控制:指的是用于在發(fā)生并發(fā)更改時(shí)確保數(shù)據(jù)一致性的特定機(jī)制。
并發(fā)標(biāo)記的實(shí)現(xiàn)是通過(guò)EF Core來(lái)實(shí)現(xiàn)并發(fā)沖突的解決,而非在數(shù)據(jù)庫(kù)層面的方案。EF Core 實(shí)現(xiàn)了樂(lè)觀并發(fā)控制(非數(shù)據(jù)庫(kù)層面),這意味著它將允許多個(gè)進(jìn)程或用戶獨(dú)立進(jìn)行更改,而不會(huì)產(chǎn)生同步或鎖定的開(kāi)銷(xiāo)。 在理想情況下,這些更改將不會(huì)相互干擾,因此都能夠成功。 在最壞的情況下,兩個(gè)或更多進(jìn)程將嘗試進(jìn)行沖突更改,而其中只有一個(gè)進(jìn)程會(huì)成功。
4.1 并發(fā)控制在 EF Core 中的工作原理:
配置為并發(fā)標(biāo)記的屬性用于實(shí)現(xiàn)樂(lè)觀并發(fā)控制:每當(dāng)在 SaveChanges 期間執(zhí)行更新或刪除操作時(shí),會(huì)將數(shù)據(jù)庫(kù)上的并發(fā)標(biāo)記屬性值與通過(guò) EF Core 讀取的原始值進(jìn)行比較:
(1) 如果這些值匹配,則可以完成該操作。
(2) 如果這些值不匹配,EF Core 會(huì)假設(shè)另一個(gè)用戶已執(zhí)行沖突操作,并中止當(dāng)前事務(wù)。這種情況被稱(chēng)為"并發(fā)沖突"。
當(dāng)配置好并發(fā)標(biāo)記后,數(shù)據(jù)庫(kù)提供程序負(fù)責(zé)實(shí)現(xiàn)并發(fā)標(biāo)記值的比較,EF Core 會(huì)對(duì)任何 UPDATE 或 DELETE 語(yǔ)句的 WHERE 子句中的并發(fā)標(biāo)記值進(jìn)行檢查。執(zhí)行這些語(yǔ)句后,EF Core 會(huì)讀取受影響的行數(shù)。如果未影響任何行,將檢測(cè)到并發(fā)沖突,并且 EF Core 會(huì)引發(fā) DbUpdateConcurrencyException。
例如,將 Person 的 LastName 配置為并發(fā)標(biāo)記。 這樣,對(duì) Person 的任何更新操作(并發(fā)標(biāo)記的屬性必須作為比較參照來(lái)反映并發(fā)沖突),都將在 WHERE 子句中做并發(fā)檢查,在數(shù)據(jù)庫(kù)端將執(zhí)行如下sql命令,條件LastName 是EF自動(dòng)加上去:
UPDATE [Person] SET [FirstName] = @p1 WHERE [PersonId] = @p0 AND [LastName] = @p2;
并發(fā)標(biāo)記后會(huì)有三組值可用于幫助解決并發(fā)沖突:
1.“當(dāng)前值”是應(yīng)用程序嘗試寫(xiě)入數(shù)據(jù)庫(kù)的值。
2.“原始值”是在進(jìn)行任何編輯之前最初從數(shù)據(jù)庫(kù)中檢索的值。
3.“數(shù)據(jù)庫(kù)值”是當(dāng)前存儲(chǔ)在數(shù)據(jù)庫(kù)中的值。
產(chǎn)生并發(fā)沖突的常規(guī)處理步驟是:
1.在 SaveChanges 期間捕獲 DbUpdateConcurrencyException。
2.使用 DbUpdateConcurrencyException.Entries 為受影響的實(shí)體準(zhǔn)備一組新更改。
3.刷新并發(fā)標(biāo)記的原始值以反映數(shù)據(jù)庫(kù)中的當(dāng)前值。
4.重試該過(guò)程,直到不發(fā)生任何沖突。
下面使用數(shù)據(jù)注釋方法將LastName屬性配置為并發(fā)標(biāo)記
public class Person
{
public int PersonId { get; set; }
[ConcurrencyCheck]
public string LastName { get; set; }
public string FirstName { get; set; }
}
下面使用Fluent API 可用于將LastName屬性配置為并發(fā)標(biāo)記
modelBuilder.Entity<Person>().Property(p => p.LastName).IsConcurrencyToken();
總結(jié):并發(fā)沖突的產(chǎn)生,一般只有在生產(chǎn)環(huán)境或模擬大量用戶線程的情況下才可能產(chǎn)生關(guān)系型數(shù)據(jù)庫(kù)的并發(fā)死鎖。產(chǎn)生死鎖的原因有很多,如未建索引導(dǎo)致表掃描、或未按同一順序訪問(wèn)對(duì)象等。只有分析出死鎖的原因( sqlserver 死鎖分析) (mysql死鎖分析),才考慮結(jié)合EF的并發(fā)標(biāo)記來(lái)解決。
參考文獻(xiàn):
官方資料: 主鍵