值轉換
值轉換器允許在讀取或寫入資料庫時轉換屬性值。 這項轉換可以是從一個值轉換成另一個相同型別的值(例如加密字串),或從某個型別的值轉換成另一個型別的值(例如,將列舉值轉換成資料庫中的字元串。
提示
您可以從 GitHub 下載範例程式碼,以執行並偵錯此文件中的所有程式碼。
概觀
值轉換器會以 和 ProviderClrType
來ModelClrType
指定。 模型類型是實體類型中屬性的 .NET 類型。 提供者類型是資料庫提供者所瞭解的 .NET 類型。 例如,若要將列舉儲存為資料庫中的字串,模型類型是列舉的類型,而提供者類型為 String
。 這兩種類型可以相同。
轉換是使用兩Func
個表達式樹狀結構來定義:一個從 ModelClrType
到 ProviderClrType
,另一個從 ProviderClrType
到 。ModelClrType
表達式樹狀結構會使用,以便編譯成數據庫存取委派,以便有效率地轉換。 表達式樹狀結構可能包含對複雜轉換之轉換方法的簡單呼叫。
注意
已針對值轉換設定的屬性也可能需要指定 ValueComparer<T>。 如需詳細資訊,請參閱下列範例和 值比較子 檔。
設定值轉換器
值轉換是在 中 DbContext.OnModelCreating設定。 例如,請考慮定義為下列專案的列舉和實體類型:
public class Rider
{
public int Id { get; set; }
public EquineBeast Mount { get; set; }
}
public enum EquineBeast
{
Donkey,
Mule,
Horse,
Unicorn
}
轉換可以設定在 中 OnModelCreating ,以將列舉值儲存為字串,例如 “Donkey”、“Mule” 等。您只需要提供一個函式,以從 ModelClrType
ProviderClrType
轉換成 ,另一個函式進行相反轉換:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
}
注意
null
值永遠不會傳遞至值轉換器。 資料庫數據行中的 Null 一律為實體實例中的 Null,反之亦然。 這可讓轉換的實作更容易,並允許在可為 Null 和不可為 Null 的屬性之間共用。 如需詳細資訊,請參閱 GitHub 問題 #13850 。
大量設定值轉換器
針對使用相關 CLR 類型的每個屬性,設定相同的值轉換器很常見。 您可以針對每個屬性手動執行這項操作,而不需要針對整個模型使用 預先慣例模型組態 來執行此動作。 若要這樣做,請將您的值轉換器定義為類別:
public class CurrencyConverter : ValueConverter<Currency, decimal>
{
public CurrencyConverter()
: base(
v => v.Amount,
v => new Currency(v))
{
}
}
然後,在您的內容類型中覆寫 ConfigureConventions ,並設定轉換器,如下所示:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<Currency>()
.HaveConversion<CurrencyConverter>();
}
預先定義的轉換
EF Core 包含許多預先定義的轉換,可避免需要手動撰寫轉換函式。 相反地,EF Core 會根據模型中的屬性類型和所要求的資料庫提供者類型,挑選要使用的轉換。
例如,列舉到字串轉換會作為上述範例使用,但 EF Core 會在提供者類型設定為 string
使用的 HasConversion泛型型別時自動執行這項操作:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion<string>();
}
藉由明確指定資料庫數據行類型,即可達到相同的目的。 例如,如果實體類型的定義如下:
public class Rider2
{
public int Id { get; set; }
[Column(TypeName = "nvarchar(24)")]
public EquineBeast Mount { get; set; }
}
然後,列舉值將會儲存為資料庫中的字串,而不需在 中進行任何進一步的 OnModelCreating設定。
ValueConverter 類別
如上所示呼叫 HasConversion 會建立 ValueConverter<TModel,TProvider> 實例,並在屬性上設定它。 ValueConverter
可以改為明確地建立 。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new ValueConverter<EquineBeast, string>(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(converter);
}
當多個屬性使用相同的轉換時,這非常有用。
內建轉換器
如上所述,EF Core 隨附一組預先定義的 ValueConverter<TModel,TProvider> 類別,可在 命名空間中找到 Microsoft.EntityFrameworkCore.Storage.ValueConversion 。 在許多情況下,EF 會根據模型中屬性的類型和資料庫中所要求的類型,選擇適當的內建轉換器,如上所示用於列舉。 例如,在 .HasConversion<int>()
屬性上使用 bool
會導致 EF Core 將布林值轉換成數值零和一個值:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<User>()
.Property(e => e.IsActive)
.HasConversion<int>();
}
這在功能上與建立內 BoolToZeroOneConverter<TProvider> 建的實例相同,並明確設定它:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new BoolToZeroOneConverter<int>();
modelBuilder
.Entity<User>()
.Property(e => e.IsActive)
.HasConversion(converter);
}
下表摘要說明從模型/屬性類型到資料庫提供者類型的常用預先定義轉換。 在資料表any_numeric_type
中,表示 、short
、、、long
uint
byte
、ushort
、ulong
、、sbyte
、char
、 decimal
float
或 double
的int
其中一個 。
模型/屬性類型 | 提供者/資料庫類型 | 轉換 | 使用方式 |
---|---|---|---|
bool | any_numeric_type | False/true 至 0/1 | .HasConversion<any_numeric_type>() |
any_numeric_type | 任兩個數位的 False/true | 使用 BoolToTwoValuesConverter<TProvider> | |
字串 | False/true 表示 “N”/“Y” | .HasConversion<string>() |
|
字串 | 任兩個字串的 False/true | 使用 BoolToStringConverter | |
any_numeric_type | bool | 0/1 至 false/true | .HasConversion<bool>() |
any_numeric_type | 簡單轉換 | .HasConversion<any_numeric_type>() |
|
字串 | 數位做為字串 | .HasConversion<string>() |
|
列舉 | any_numeric_type | 列舉的數值 | .HasConversion<any_numeric_type>() |
字串 | 列舉值的字串表示 | .HasConversion<string>() |
|
字串 | bool | 將字串剖析為bool | .HasConversion<bool>() |
any_numeric_type | 將字串剖析為指定的數值類型 | .HasConversion<any_numeric_type>() |
|
char | 字串的第一個字元 | .HasConversion<char>() |
|
Datetime | 將字串剖析為 DateTime | .HasConversion<DateTime>() |
|
DateTimeOffset | 將字串剖析為 DateTimeOffset | .HasConversion<DateTimeOffset>() |
|
TimeSpan | 將字串剖析為 TimeSpan | .HasConversion<TimeSpan>() |
|
Guid | 將字串剖析為 Guid | .HasConversion<Guid>() |
|
byte[] | 字串為UTF8位元組 | .HasConversion<byte[]>() |
|
char | 字串 | 單一字元字串 | .HasConversion<string>() |
Datetime | long | 編碼的日期/時間保留DateTime.Kind | .HasConversion<long>() |
long | 刻度 | 使用 DateTimeToTicksConverter | |
字串 | 不因文化特性而異的日期/時間字串 | .HasConversion<string>() |
|
DateTimeOffset | long | 具有位移的編碼日期/時間 | .HasConversion<long>() |
字串 | 具有位移的不變異文化特性日期/時間字串 | .HasConversion<string>() |
|
TimeSpan | long | 刻度 | .HasConversion<long>() |
字串 | 不因文化特性時間範圍字串而異 | .HasConversion<string>() |
|
URI | 字串 | URI 做為字串 | .HasConversion<string>() |
PhysicalAddress | 字串 | 位址做為字串 | .HasConversion<string>() |
byte[] | 以大端網路順序排列的位元組 | .HasConversion<byte[]>() |
|
IPAddress | 字串 | 位址做為字串 | .HasConversion<string>() |
byte[] | 以大端網路順序排列的位元組 | .HasConversion<byte[]>() |
|
Guid | 字串 | 'dd-d-d-d-d' 格式的 GUID | .HasConversion<string>() |
byte[] | .NET 二進位串行化順序中的位元組 | .HasConversion<byte[]>() |
請注意,這些轉換假設值的格式適用於轉換。 例如,如果字串值無法剖析為數位,將字串轉換成數位將會失敗。
內建轉換器的完整清單如下:
- 轉換布林屬性:
- BoolToStringConverter - 布爾至字串,例如 “N” 和 “Y”
- BoolToTwoValuesConverter<TProvider> - 布爾值至任兩個值
- BoolToZeroOneConverter<TProvider> - 布爾值到零和一
- 轉換位元組數組屬性:
- BytesToStringConverter - 以Base64編碼字串的位元組數位
- 只需要類型轉換的任何轉換
- CastingConverter<TModel,TProvider> - 只需要類型轉換的轉換
- 轉換 char 屬性:
- CharToStringConverter - 字元到單一字元字串
- 轉換 DateTimeOffset 屬性:
- DateTimeOffsetToBinaryConverter - DateTimeOffset 至二進位編碼的64位值
- DateTimeOffsetToBytesConverter - DateTimeOffset 至位元組陣列
- DateTimeOffsetToStringConverter - DateTimeOffset to string
- 轉換 DateTime 屬性:
- DateTimeToBinaryConverter - DateTime 至 64 位值,包括 DateTimeKind
- DateTimeToStringConverter - DateTime to string
- DateTimeToTicksConverter - DateTime 表示刻度
- 轉換欄位屬性:
- EnumToNumberConverter<TEnum,TNumber> - 基礎編號的列舉
- EnumToStringConverter<TEnum> - 字串列舉
- 轉換 Guid 屬性:
- GuidToBytesConverter - Guid 至位元組陣列
- GuidToStringConverter - Guid to string
- 轉換 IPAddress 屬性:
- IPAddressToBytesConverter - IPAddress 至位元組陣列
- IPAddressToStringConverter - IPAddress to string
- 轉換數值 (int、double、decimal 等) 屬性:
- NumberToBytesConverter<TNumber> - 位元組陣列的任何數值
- NumberToStringConverter<TNumber> - 字串的任何數值
- 轉換 PhysicalAddress 屬性:
- PhysicalAddressToBytesConverter - PhysicalAddress 至位元組陣列
- PhysicalAddressToStringConverter - PhysicalAddress to string
- 轉換字串屬性:
- StringToBoolConverter - 字串,例如 “N” 和 “Y” 至 bool
- StringToBytesConverter - 字串到UTF8位元組
- StringToCharConverter - 字串到字元
- StringToDateTimeConverter - 字串至 DateTime
- StringToDateTimeOffsetConverter - 字串至 DateTimeOffset
- StringToEnumConverter<TEnum> - 要列舉的字串
- StringToGuidConverter - 字串至 Guid
- StringToNumberConverter<TNumber> - 字串到數值類型
- StringToTimeSpanConverter - 字串至 TimeSpan
- StringToUriConverter - 字串至 Uri
- 轉換 TimeSpan 屬性:
- TimeSpanToStringConverter - TimeSpan to string
- TimeSpanToTicksConverter - TimeSpan 表示刻度
- 轉換 Uri 屬性:
- UriToStringConverter - Uri to string
請注意,所有內建轉換器都是無狀態的,因此單一實例可以由多個屬性安全地共用。
數據行 Facet 和對應提示
某些資料庫類型具有可修改數據儲存方式的 Facet。 包括:
- 小數點和日期/時間數據行的有效位數和小數字數
- 二進位和字串數據行的大小/長度
- 字串數據行的 Unicode
這些 Facet 可以針對使用值轉換器的屬性,以一般方式設定,而且會套用至轉換的資料庫類型。 例如,從列舉轉換成字串時,我們可以指定資料庫數據行應該是非 Unicode,並儲存最多 20 個字元:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion<string>()
.HasMaxLength(20)
.IsUnicode(false);
}
或者,明確建立轉換器時:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new ValueConverter<EquineBeast, string>(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(converter)
.HasMaxLength(20)
.IsUnicode(false);
}
這會在針對 SQL Server 使用 EF Core 移轉時產生 varchar(20)
資料行:
CREATE TABLE [Rider] (
[Id] int NOT NULL IDENTITY,
[Mount] varchar(20) NOT NULL,
CONSTRAINT [PK_Rider] PRIMARY KEY ([Id]));
不過,如果根據預設, EquineBeast
所有資料行都應該是 varchar(20)
,則可以將這項資訊提供給值轉換器做為 ConverterMappingHints。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new ValueConverter<EquineBeast, string>(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v),
new ConverterMappingHints(size: 20, unicode: false));
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(converter);
}
現在,每當使用此轉換器時,資料庫數據行會是非 Unicode,長度上限為 20。 不過,這些只是提示,因為它們是由對應屬性上明確設定的任何 Facet 覆寫。
範例
簡單值物件
這個範例會使用簡單類型來包裝基本類型。 當您想要模型中的類型比基本類型更明確(因此更安全類型)時,這非常有用。 在此範例中,該類型為 Dollars
,它會包裝小數點基本類型:
public readonly struct Dollars
{
public Dollars(decimal amount)
=> Amount = amount;
public decimal Amount { get; }
public override string ToString()
=> $"${Amount}";
}
這可用於實體類型:
public class Order
{
public int Id { get; set; }
public Dollars Price { get; set; }
}
當儲存在資料庫中時,會轉換成基礎 decimal
:
modelBuilder.Entity<Order>()
.Property(e => e.Price)
.HasConversion(
v => v.Amount,
v => new Dollars(v));
複合值物件
在上一個範例中,value 物件類型只包含單一屬性。 實值物件類型比較常見,以組成組成定義域概念的多個屬性。 例如,包含金額和貨幣的一般 Money
類型:
public readonly struct Money
{
[JsonConstructor]
public Money(decimal amount, Currency currency)
{
Amount = amount;
Currency = currency;
}
public override string ToString()
=> (Currency == Currency.UsDollars ? "$" : "£") + Amount;
public decimal Amount { get; }
public Currency Currency { get; }
}
public enum Currency
{
UsDollars,
PoundsSterling
}
這個值物件可以在實體類型中使用,如之前所示:
public class Order
{
public int Id { get; set; }
public Money Price { get; set; }
}
值轉換器目前只能從單一資料庫數據行來回轉換值。 這項限制表示物件中的所有屬性值都必須編碼為單一數據行值。 這通常是藉由串行化對象進入資料庫時處理,然後在出路時再次還原串行化它。例如,使用 System.Text.Json:
modelBuilder.Entity<Order>()
.Property(e => e.Price)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<Money>(v, (JsonSerializerOptions)null));
注意
我們計劃在未來版本的 EF Core 中允許將對象對應到多個數據行,而不需要在這裡使用串行化。 GitHub 問題 #13947 會追蹤此問題。
基本類型的集合
串行化也可以用來儲存基本值集合。 例如:
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Contents { get; set; }
public ICollection<string> Tags { get; set; }
}
再次使用 System.Text.Json :
modelBuilder.Entity<Post>()
.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions)null),
new ValueComparer<ICollection<string>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (ICollection<string>)c.ToList()));
ICollection<string>
表示可變動的參考型別。 這表示需要 , ValueComparer<T> 讓EF Core 能夠正確追蹤及偵測變更。 如需詳細資訊,請參閱 值比較子 。
值物件的集合
將前兩個範例結合在一起,我們可以建立 value 物件的集合。 例如,請考慮建立 AnnualFinance
部落格財務模型一年的類型:
public readonly struct AnnualFinance
{
[JsonConstructor]
public AnnualFinance(int year, Money income, Money expenses)
{
Year = year;
Income = income;
Expenses = expenses;
}
public int Year { get; }
public Money Income { get; }
public Money Expenses { get; }
public Money Revenue => new Money(Income.Amount - Expenses.Amount, Income.Currency);
}
此類型是由我們先前建立的數種 Money
類型所組成:
public readonly struct Money
{
[JsonConstructor]
public Money(decimal amount, Currency currency)
{
Amount = amount;
Currency = currency;
}
public override string ToString()
=> (Currency == Currency.UsDollars ? "$" : "£") + Amount;
public decimal Amount { get; }
public Currency Currency { get; }
}
public enum Currency
{
UsDollars,
PoundsSterling
}
然後,我們可以將 的 AnnualFinance
集合新增至實體類型:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public IList<AnnualFinance> Finances { get; set; }
}
再次使用串行化來儲存下列專案:
modelBuilder.Entity<Blog>()
.Property(e => e.Finances)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<AnnualFinance>>(v, (JsonSerializerOptions)null),
new ValueComparer<IList<AnnualFinance>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<AnnualFinance>)c.ToList()));
注意
和之前一 ValueComparer<T>樣,此轉換需要 。 如需詳細資訊,請參閱 值比較子 。
將物件值為索引鍵
有時候基本索引鍵屬性可能會包裝在值物件中,以在指派值時新增額外的類型安全性層級。 例如,我們可以實作部落格的金鑰類型,以及文章的索引鍵類型:
public readonly struct BlogKey
{
public BlogKey(int id) => Id = id;
public int Id { get; }
}
public readonly struct PostKey
{
public PostKey(int id) => Id = id;
public int Id { get; }
}
然後,這些可以在領域模型中使用:
public class Blog
{
public BlogKey Id { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts { get; set; }
}
public class Post
{
public PostKey Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public BlogKey? BlogId { get; set; }
public Blog Blog { get; set; }
}
請注意,Blog.Id
無法不小心指派 PostKey
,而且Post.Id
無法不小心指派 。BlogKey
同樣地, Post.BlogId
必須指派 BlogKey
外鍵屬性。
注意
顯示此模式並不表示我們建議使用。 請仔細考慮這種抽象概念層級是否有助於或阻礙您的開發體驗。 此外,請考慮使用導覽和產生的索引鍵,而不是直接處理索引鍵值。
然後可以使用值轉換器來對應這些索引鍵屬性:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var blogKeyConverter = new ValueConverter<BlogKey, int>(
v => v.Id,
v => new BlogKey(v));
modelBuilder.Entity<Blog>().Property(e => e.Id).HasConversion(blogKeyConverter);
modelBuilder.Entity<Post>(
b =>
{
b.Property(e => e.Id).HasConversion(v => v.Id, v => new PostKey(v));
b.Property(e => e.BlogId).HasConversion(blogKeyConverter);
});
}
注意
具有轉換的索引鍵屬性只能使用從 EF Core 7.0 開始產生的索引鍵值。
針對 timestamp/rowversion 使用 ulong
SQL Server 支援使用 8 位元組二進位timestamp
rowversion
/資料行的自動開放式並行存取。 這些一律會使用8位元組數組從資料庫讀取和寫入資料庫。 不過,位元組陣列是可變動的參考型別,因此處理它們會有些痛苦。 值轉換器可改為 rowversion
對應至 ulong
屬性,這比位元組陣列更合適且容易使用。 例如,請考慮 Blog
具有 ulong 並行令牌的實體:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public ulong Version { get; set; }
}
這可以使用值轉換器對應至 SQL Server rowversion
資料列:
modelBuilder.Entity<Blog>()
.Property(e => e.Version)
.IsRowVersion()
.HasConversion<byte[]>();
讀取日期時指定 DateTime.Kind
將 儲存DateTime為 datetime
或 datetime2
時,DateTime.KindSQL Server 會捨棄 旗標。 這表示從資料庫傳回的 DateTime 值一律具有 DateTimeKind 的 Unspecified
。
值轉換器可以用兩種方式來處理。 首先,EF Core 具有值轉換器,可建立 8 位元組不透明值,以保留 Kind
旗標。 例如:
modelBuilder.Entity<Post>()
.Property(e => e.PostedOn)
.HasConversion<long>();
這可讓資料庫中具有不同 Kind
旗標的 DateTime 值混合。
此方法的問題在於資料庫不再具有可 datetime
辨識或 datetime2
數據行。 因此,通常一律儲存 UTC 時間(或較不常見,一律是當地時間),然後使用值轉換器忽略 Kind
旗標或將它設定為適當的值。 例如,下列轉換器可確保 DateTime
從資料庫讀取的值會有 DateTimeKind UTC
:
modelBuilder.Entity<Post>()
.Property(e => e.LastUpdated)
.HasConversion(
v => v,
v => new DateTime(v.Ticks, DateTimeKind.Utc));
如果在實體實例中設定了本機和UTC值的混合,則轉換器可用來在插入之前適當地轉換。 例如:
modelBuilder.Entity<Post>()
.Property(e => e.LastUpdated)
.HasConversion(
v => v.ToUniversalTime(),
v => new DateTime(v.Ticks, DateTimeKind.Utc));
注意
請仔細考慮將所有數據庫存取碼統一為一直使用 UTC 時間,只在向使用者呈現數據時處理當地時間。
使用不區分大小寫的字串索引鍵
某些資料庫,包括 SQL Server,預設會執行不區分大小寫的字串比較。 另一方面,.NET 預設會執行區分大小寫的字串比較。 這表示,類似 「DotNet」 的外鍵值會比對 SQL Server 上的主鍵值 「dotnet」,但在 EF Core 中則不相符。 索引鍵的值比較子可用來強制 EF Core 不區分大小寫的字串比較,例如在資料庫中。 例如,請考慮使用字串索引鍵的部落格/文章模型:
public class Blog
{
public string Id { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts { get; set; }
}
public class Post
{
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public string BlogId { get; set; }
public Blog Blog { get; set; }
}
如果某些值有不同的大小寫, Post.BlogId
這將無法如預期般運作。 由此造成的錯誤將取決於應用程式正在執行的動作,但通常牽涉到未 正確修正 的物件圖形,以及/或因為 FK 值錯誤而失敗的更新。 值比較子可用來更正下列專案:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var comparer = new ValueComparer<string>(
(l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
v => v.ToUpper().GetHashCode(),
v => v);
modelBuilder.Entity<Blog>()
.Property(e => e.Id)
.Metadata.SetValueComparer(comparer);
modelBuilder.Entity<Post>(
b =>
{
b.Property(e => e.Id).Metadata.SetValueComparer(comparer);
b.Property(e => e.BlogId).Metadata.SetValueComparer(comparer);
});
}
注意
.NET 字串比較和資料庫字串比較在區分大小寫時可能不同。 此模式適用於簡單的 ASCII 金鑰,但對於具有任何特定文化特性字元的索引鍵可能會失敗。 如需詳細資訊,請參閱 定序和區分大小寫 。
處理固定長度的資料庫字串
上述範例不需要值轉換器。 不過,轉換器對於或 nchar(20)
之類的char(20)
固定長度資料庫字串類型很有用。 每當值插入資料庫時,固定長度字串會填補到其完整長度。 這表示會將 「dotnet
」 的索引鍵值從資料庫讀回為 “dotnet..............
”,其中 .
代表空格符。 這樣就不會正確地與未填補的索引鍵值進行比較。
值轉換器可用來在讀取索引鍵值時修剪填補。 這可以與上一個範例中的值比較子結合,以正確比較固定長度不區分大小寫的 ASCII 索引鍵。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new ValueConverter<string, string>(
v => v,
v => v.Trim());
var comparer = new ValueComparer<string>(
(l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
v => v.ToUpper().GetHashCode(),
v => v);
modelBuilder.Entity<Blog>()
.Property(e => e.Id)
.HasColumnType("char(20)")
.HasConversion(converter, comparer);
modelBuilder.Entity<Post>(
b =>
{
b.Property(e => e.Id).HasColumnType("char(20)").HasConversion(converter, comparer);
b.Property(e => e.BlogId).HasColumnType("char(20)").HasConversion(converter, comparer);
});
}
加密屬性值
值轉換器可用來在將屬性值傳送至資料庫之前加密屬性值,然後在出路時將其解密。例如,使用字串反轉作為實際加密演算法的替代方式:
modelBuilder.Entity<User>().Property(e => e.Password).HasConversion(
v => new string(v.Reverse().ToArray()),
v => new string(v.Reverse().ToArray()));
注意
目前無法從值轉換器內取得目前 DbContext 或其他會話狀態的參考。 這會限制可使用的加密類型。 投票給 GitHub 問題 #11597 ,以移除此限制。
警告
如果您擲回自己的加密來保護敏感數據,請務必瞭解所有含意。 請考慮改用預先建置的加密機制,例如 SQL Server 上的 Always Encrypted 。
限制
值轉換系統目前有一些已知的限制:
- 如上所述,
null
無法轉換。 如果這是您需要的問題,請投票 (👍) 作為 GitHub 問題 #13850 。 - 您無法查詢到值轉換的屬性,例如 LINQ 查詢中值轉換 .NET 類型上的參考成員。 如果這是您需要的問題,請投票給👍 GitHub 問題 #10434 ,但請考慮改用 JSON 數據行。
- 目前無法將一個屬性的轉換分散到多個數據行,反之亦然。 如果這是您需要的問題,請投票 (👍) 作為 GitHub 問題 #13947 。
- 大部分透過值轉換器對應的索引鍵不支持產生值。 如果這是您需要的問題,請投票 (👍) 作為 GitHub 問題 #11597 。
- 值轉換無法參考目前的 DbContext 實例。 如果這是您需要的問題,請投票 (👍) 作為 GitHub 問題 #12205 。
- 使用值轉換類型的參數目前無法在原始 SQL API 中使用。 如果這是您需要的問題,請投票 (👍) 作為 GitHub 問題 #27534 。
未來版本會考慮移除這些限制。