值比較子
提示
此檔中的程式碼可在 GitHub 上找到,做為 可執行檔範例 。
背景
變更追蹤 表示 EF Core 會自動判斷應用程式在載入的實體實例上執行的變更,以便在呼叫 時 SaveChanges 將這些變更儲存回資料庫。 EF Core 通常會在從資料庫載入實例時擷取 實例的快照 集,並將 該快照集與交給應用程式的實例進行比較 來執行此作業。
EF Core 隨附內建邏輯來快照集和比較資料庫中所使用的大部分標準類型,因此使用者通常不需要擔心本主題。 不過,當屬性透過 值轉換器 對應時,EF Core 必須對可能很複雜的任意使用者類型執行比較。 根據預設,EF Core 會使用類型所定義的預設相等比較(例如 Equals
方法):針對快照集, 會複製實值型別來產生快照集,而參考 型 別則不會進行複製,而相同的實例會作為快照集使用。
如果內建比較行為不合適,使用者可能會提供 值比較子 ,其中包含快照集、比較和計算雜湊碼的邏輯。 例如,下列會設定屬性的值轉換 List<int>
,以轉換成資料庫中的 JSON 字串,並定義適當的值比較子:
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyListProperty)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions)null),
new ValueComparer<List<int>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()));
如需進一步的詳細資料, 請參閱 下面的可變動類別。
請注意,判斷兩個索引鍵值在解析關聯性時是否相同時,也會使用值比較子;如下所述。
淺層與深層比較
對於小型、不可變的實值型別,例如 int
,EF Core 的預設邏輯運作良好:當快照集時,會依現時複製值,並與類型的內建相等比較進行比較。 實作您自己的值比較子時,請務必考慮深層或淺層比較(以及快照集)邏輯是否適當。
請考慮位元組陣列,它可以任意大。 您可以比較這些專案:
- 根據參考,只有在使用新的位元組陣列時,才會偵測到差異
- 藉由深入比較,就會偵測到陣列中位元組的突變
根據預設,EF Core 會針對非索引鍵位元組陣列使用上述其中一種方法。 也就是說,只有比較參考,而且只有在現有的位元組陣列取代為新的位元組陣列時,才會偵測到變更。 這是一個務實的決策,可避免複製整個陣列,並在執行 SaveChanges 時比較位元組對位元組。 這表示以高效能的方式將一個影像取代為另一個影像的常見案例。
另一方面,當位元組陣列用來表示二進位索引鍵時,參考相等將無法運作,因為 FK 屬性不太可能設定 為與它需要比較的 PK 屬性相同的實例 。 因此,EF Core 會針對做為索引鍵的位元組陣列使用深層比較;由於二進位索引鍵通常很短,因此不太可能有大幅的效能命中。
請注意,選擇的比較和快照集邏輯必須彼此對應:深層比較需要深度快照集才能正常運作。
簡單不可變類別
請考慮使用值轉換器來對應簡單且不可變類別的屬性。
public sealed class ImmutableClass
{
public ImmutableClass(int value)
{
Value = value;
}
public int Value { get; }
private bool Equals(ImmutableClass other)
=> Value == other.Value;
public override bool Equals(object obj)
=> ReferenceEquals(this, obj) || obj is ImmutableClass other && Equals(other);
public override int GetHashCode()
=> Value.GetHashCode();
}
modelBuilder
.Entity<MyEntityType>()
.Property(e => e.MyProperty)
.HasConversion(
v => v.Value,
v => new ImmutableClass(v));
此類型的屬性不需要特殊比較或快照集,因為:
- 已覆寫相等,讓不同的實例正確比較
- 此類型是不可變的,因此不可能變更快照集值
因此,在此案例中,EF Core 的預設行為會正常運作。
簡單不可變的結構
簡單結構的對應也很簡單,不需要特殊的比較子或快照集。
public readonly struct ImmutableStruct
{
public ImmutableStruct(int value)
{
Value = value;
}
public int Value { get; }
}
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyProperty)
.HasConversion(
v => v.Value,
v => new ImmutableStruct(v));
EF Core 內建支援產生已編譯、成員式結構屬性的比較。 這表示結構不需要針對 EF Core 覆寫相等,但您仍可能基於其他原因 選擇這麼做 。 此外,不需要特殊快照集,因為結構是不可變的,而且一律會以成員方式複製。 (這也適用于可變結構,但 一般應避免 可變結構。
可變動類別
建議您盡可能使用不可變的類型(類別或結構)搭配值轉換器。 這通常更有效率,而且具有比使用可變動類型更簡潔的語意。 不過,也就是說,通常會使用應用程式無法變更的類型屬性。 例如,對應包含數位清單的屬性:
public List<int> MyListProperty { get; set; }
List<T> 類別:
- 具有參考相等;包含相同值的兩個清單會被視為不同的。
- 可變動;您可以在清單中新增和移除值。
清單屬性上的一般值轉換可能會將清單轉換成 JSON,以及從 JSON 轉換:
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyListProperty)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions)null),
new ValueComparer<List<int>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()));
建構函 ValueComparer<T> 式接受三個運算式:
- 檢查相等的運算式
- 產生雜湊碼的運算式
- 要建立值的運算式
在此情況下,比較是藉由檢查數位序列是否相同來完成。
同樣地,雜湊程式碼是從這個相同的序列建置的。 (請注意,這是可變動值的雜湊碼,因此可能會導致 問題 。如果可以的話,請改為不可變。
使用 複製清單 ToList
來建立快照集。 同樣地,只有當清單要變動時,才需要此專案。 如果可以的話,請改為不可變。
注意
值轉換器和比較子是使用運算式來建構,而不是簡單的委派。 這是因為 EF Core 會將這些運算式插入更複雜的運算式樹狀結構,然後編譯成實體 Shaper 委派。 就概念上講,這類似于編譯器內嵌。 例如,簡單的轉換可能只是在轉換中編譯,而不是呼叫另一個方法來執行轉換。
索引鍵比較子
背景區段涵蓋索引鍵比較可能需要特殊語意的原因。 請務必在主要、主體或外鍵屬性上設定索引鍵時,建立適合索引鍵的比較子。
在相同屬性上需要不同語意的罕見情況下使用 SetKeyValueComparer 。
注意
SetStructuralValueComparer 已經過時。 請改用 SetKeyValueComparer。
覆寫預設比較子
有時候 EF Core 所使用的預設比較可能不合適。 例如,根據預設,在 EF Core 中偵測到位元組陣列的突變不是。 您可以藉由在 屬性上設定不同的比較子來覆寫:
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyBytes)
.Metadata
.SetValueComparer(
new ValueComparer<byte[]>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToArray()));
EF Core 現在會比較位元組序列,因此會偵測位元組陣列突變。